From 63297cdc0bfa64005dc589877170a63ccb28ed31 Mon Sep 17 00:00:00 2001 From: kuviman Date: Fri, 31 Jan 2025 19:56:44 +0400 Subject: [PATCH 1/4] specify opens in bundle.bat --- code/bundle.bat | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/bundle.bat b/code/bundle.bat index 19368e8..10266af 100644 --- a/code/bundle.bat +++ b/code/bundle.bat @@ -5,5 +5,7 @@ if not "!JAVA11_64_HOME!"=="" ( set JAVA_HOME=!JAVA11_64_HOME! ) +set JDK_JAVA_OPTIONS="--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED" + call mvn.cmd -Dfile.encoding=UTF-8 validate call mvn.cmd -Dfile.encoding=UTF-8 -DcreateChecksum=true clean source:jar javadoc:jar repository:bundle-create install --batch-mode %* From ae247cd931428e7075cb8bab5401030111458542 Mon Sep 17 00:00:00 2001 From: kuviman Date: Fri, 31 Jan 2025 19:57:22 +0400 Subject: [PATCH 2/4] Support object?.field to return null if object is null in MethodArgumentsFormatUtil --- .../reflection/MethodArgumentsFormatUtil.java | 81 ++++++++++++++----- .../MethodArgumentsFormatUtilTest.java | 2 + 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/code/src/main/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtil.java b/code/src/main/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtil.java index 530d110..ee19290 100644 --- a/code/src/main/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtil.java +++ b/code/src/main/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtil.java @@ -14,6 +14,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; @@ -108,24 +109,12 @@ private static Annotation[][] getParameterAnnotations(Method method) { private static Object getNamedParameterValue(String expression, int namedParameterCount, String[] parameterNames, Object[] namedParameterValues) { - String[] tokens = Patterns.DOT_PATTERN.split(expression); - - if (tokens.length == 0) { - throw new IllegalArgumentException("Expression `" + expression + "` is not formatted properly."); - } - for (String token : tokens) { - if (token.isEmpty()) { - throw new IllegalArgumentException("Expression `" + expression + "` is not formatted properly."); - } - if (!JAVA_IDENTIFIER_PATTERN.matcher(token).matches()) { - throw new IllegalArgumentException("Expression `" + expression + "` is not formatted properly."); - } - } + ExpressionPart[] parts = parseExpression(expression); Object object = null; boolean found = false; for (int i = 0; i < namedParameterCount; i++) { - if (parameterNames[i].equals(tokens[0])) { + if (parameterNames[i].equals(parts[0].getName())) { if (found) { throw new IllegalArgumentException("Parameter names should be unique, but `" + parameterNames[i] + "` seems not to be unique."); @@ -136,27 +125,59 @@ private static Object getNamedParameterValue(String expression, int namedParamet } if (!found) { - throw new IllegalArgumentException("Unable to find parameter named `" + tokens[0] + "` (use @Name)."); + throw new IllegalArgumentException("Unable to find parameter named `" + parts[0].getName() + "` (use @Name)."); } if (object == null) { - if (tokens.length == 1) { + if (parts.length == 1) { return "null"; - } else { - throw new NullPointerException("Parameter `" + tokens[0] + "` is null and can't be used to get it's property."); } } - for (int i = 1; i < tokens.length; i++) { + for (int i = 1; i < parts.length; i++) { if (object == null) { - throw new NullPointerException("Parameter `" + tokens[0] + "` has null before property `" + tokens[i] + "`."); + if (parts[i].isNullGuard()) { + return null; + } + throw new NullPointerException("Parameter `" + parts[0].getName() + "` has null before property `" + parts[i].getName() + "`."); } - object = getProperty(object, tokens[i]); + object = getProperty(object, parts[i].getName()); } return object; } + private static @NotNull ExpressionPart[] parseExpression(String expression) { + ArrayList parts = new ArrayList<>(); + int prev = 0; + boolean hadNullGuard = false; + for (int i = 0; i < expression.length(); i++) { + if (expression.charAt(i) == '.') { + parts.add(new ExpressionPart(expression.substring(prev, i), hadNullGuard)); + prev = i + 1; + hadNullGuard = false; + } else if (expression.charAt(i) == '?') { + if (i + 1 >= expression.length() || expression.charAt(i + 1) != '.') { + throw new IllegalArgumentException("Expression `" + expression + "` is not formatted properly."); + } + parts.add(new ExpressionPart(expression.substring(prev, i), hadNullGuard)); + i++; + prev = i + 1; + hadNullGuard = true; + } + } + parts.add(new ExpressionPart(expression.substring(prev), hadNullGuard)); + for (ExpressionPart part : parts) { + if (part.getName().isEmpty()) { + throw new IllegalArgumentException("Expression `" + expression + "` is not formatted properly."); + } + if (!JAVA_IDENTIFIER_PATTERN.matcher(part.getName()).matches()) { + throw new IllegalArgumentException("Expression `" + expression + "` is not formatted properly."); + } + } + return parts.toArray(new ExpressionPart[0]); + } + private static FastMethod getPropertyMethod(Class clazz, String property) { ClassAndProperty classAndProperty = new ClassAndProperty(clazz, property); FastMethod method = methodByProperty.get(classAndProperty); @@ -239,5 +260,23 @@ public int hashCode() { return result; } } + + private static final class ExpressionPart { + private final String name; + private final boolean nullGuard; + + public ExpressionPart(String name, boolean nullGuard) { + this.name = name; + this.nullGuard = nullGuard; + } + + public String getName() { + return name; + } + + public boolean isNullGuard() { + return nullGuard; + } + } } diff --git a/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java b/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java index fc8b8db..d081340 100644 --- a/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java +++ b/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java @@ -35,6 +35,8 @@ public void testFormat() throws NoSuchMethodException { Assert.assertEquals("null13", MethodArgumentsFormatUtil.format("${index}${value}${country.name}", method, new Object[] {userC, null, 13, countryA})); Assert.assertEquals("null13Страна", MethodArgumentsFormatUtil.format("${index}${value}${country.name}", method, new Object[] {userC, null, 13, countryC})); + Assert.assertEquals("userId=null", MethodArgumentsFormatUtil.format("userId=${firstUser?.id}", method, new Object[] {userC, 32, 13, countryB})); + Assert.assertEquals("Cache-user:123,true,Russia and null[32]", MethodArgumentsFormatUtil.format("Cache-user:${firstUser.id},${firstUser.male},${firstUser.country.name} and ${country.name}[${index}]", method, new Object[] {userA, 32, 13, countryB})); Assert.assertEquals("Cache-user:-32,false,US and Страна[-1]", MethodArgumentsFormatUtil.format("Cache-user:${firstUser.id},${firstUser.male},${firstUser.country.name} and ${country.name}[${index}]", method, new Object[] {userB, -1, -2, countryC})); Assert.assertEquals("Cache-user:123,true,6 and null[32]", MethodArgumentsFormatUtil.format("Cache-user:${firstUser.id},${firstUser.male},${firstUser.country.name.length} and ${country.name}[${index}]", method, new Object[] {userA, 32, 13, countryB})); From 03b9b900f4d0ef69b37c318b12117a12458d3156 Mon Sep 17 00:00:00 2001 From: kuviman Date: Fri, 31 Jan 2025 20:19:08 +0400 Subject: [PATCH 3/4] More tests --- .../commons/reflection/MethodArgumentsFormatUtilTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java b/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java index d081340..9d5909e 100644 --- a/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java +++ b/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java @@ -29,6 +29,7 @@ public void testFormat() throws NoSuchMethodException { Country countryA = new Country(""); Country countryB = new Country(null); Country countryC = new Country("Страна"); + Country countryNull = null; Assert.assertEquals("user=null", MethodArgumentsFormatUtil.format("user=${firstUser}", method, new Object[] {userC, null, 13, countryB})); Assert.assertEquals("null13Country{name='null'}", MethodArgumentsFormatUtil.format("${index}${value}${country}", method, new Object[] {userC, null, 13, countryB})); @@ -36,6 +37,12 @@ public void testFormat() throws NoSuchMethodException { Assert.assertEquals("null13Страна", MethodArgumentsFormatUtil.format("${index}${value}${country.name}", method, new Object[] {userC, null, 13, countryC})); Assert.assertEquals("userId=null", MethodArgumentsFormatUtil.format("userId=${firstUser?.id}", method, new Object[] {userC, 32, 13, countryB})); + Assert.assertThrows(NullPointerException.class, () -> MethodArgumentsFormatUtil.format("userId=${firstUser.id}", method, new Object[] {userC, 32, 13, countryB})); + Assert.assertEquals("countryNameLength=null", MethodArgumentsFormatUtil.format("countryNameLength=${country?.name?.length}", method, new Object[] {userC, 32, 13, countryNull})); + Assert.assertEquals("countryNameLength=null", MethodArgumentsFormatUtil.format("countryNameLength=${country?.name.length}", method, new Object[] {userC, 32, 13, countryNull})); + Assert.assertEquals("countryNameLength=null", MethodArgumentsFormatUtil.format("countryNameLength=${country?.name?.length}", method, new Object[] {userC, 32, 13, countryB})); + Assert.assertThrows(NullPointerException.class, () -> MethodArgumentsFormatUtil.format("countryNameLength=${country.name?.length}", method, new Object[] {userC, 32, 13, countryNull})); + Assert.assertThrows(NullPointerException.class, () -> MethodArgumentsFormatUtil.format("countryNameLength=${country?.name.length}", method, new Object[] {userC, 32, 13, countryB})); Assert.assertEquals("Cache-user:123,true,Russia and null[32]", MethodArgumentsFormatUtil.format("Cache-user:${firstUser.id},${firstUser.male},${firstUser.country.name} and ${country.name}[${index}]", method, new Object[] {userA, 32, 13, countryB})); Assert.assertEquals("Cache-user:-32,false,US and Страна[-1]", MethodArgumentsFormatUtil.format("Cache-user:${firstUser.id},${firstUser.male},${firstUser.country.name} and ${country.name}[${index}]", method, new Object[] {userB, -1, -2, countryC})); From 27c136edf41aa383dbb25330c6f45ed6cf78a029 Mon Sep 17 00:00:00 2001 From: mikemirzayanov Date: Sat, 1 Feb 2025 16:26:15 +0300 Subject: [PATCH 4/4] MethodArgumentsFormatUtil: small changes --- .../reflection/MethodArgumentsFormatUtil.java | 28 +++++++++++-------- .../MethodArgumentsFormatUtilTest.java | 5 ++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/code/src/main/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtil.java b/code/src/main/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtil.java index ee19290..6ab8161 100644 --- a/code/src/main/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtil.java +++ b/code/src/main/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtil.java @@ -1,6 +1,5 @@ package com.codeforces.commons.reflection; -import com.codeforces.commons.text.Patterns; import com.codeforces.commons.text.StringUtil; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -15,6 +14,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; @@ -37,6 +37,7 @@ public class MethodArgumentsFormatUtil { .expireAfterWrite(1, TimeUnit.HOURS) .build( new CacheLoader() { + @SuppressWarnings("NullableProblems") public Annotation[][] load(@NotNull Method method) { return method.getParameterAnnotations(); } @@ -87,7 +88,8 @@ public static String format(String pattern, Method method, Object[] args) { } if (end < length) { - result.append(getNamedParameterValue(pattern.substring(i + 2, end), namedParameterCount, parameterNames, namedParameterValues)); + result.append(getNamedParameterValue(pattern.substring(i + 2, end), + namedParameterCount, parameterNames, namedParameterValues)); i = end; continue; } @@ -125,13 +127,12 @@ private static Object getNamedParameterValue(String expression, int namedParamet } if (!found) { - throw new IllegalArgumentException("Unable to find parameter named `" + parts[0].getName() + "` (use @Name)."); + throw new IllegalArgumentException("Unable to find parameter named `" + + parts[0].getName() + "` (use @Name)."); } - if (object == null) { - if (parts.length == 1) { - return "null"; - } + if (object == null && parts.length == 1) { + return "null"; } for (int i = 1; i < parts.length; i++) { @@ -139,7 +140,8 @@ private static Object getNamedParameterValue(String expression, int namedParamet if (parts[i].isNullGuard()) { return null; } - throw new NullPointerException("Parameter `" + parts[0].getName() + "` has null before property `" + parts[i].getName() + "`."); + throw new NullPointerException("Parameter `" + parts[0].getName() + + "` has null before property `" + parts[i].getName() + "`."); } object = getProperty(object, parts[i].getName()); } @@ -148,7 +150,8 @@ private static Object getNamedParameterValue(String expression, int namedParamet } private static @NotNull ExpressionPart[] parseExpression(String expression) { - ArrayList parts = new ArrayList<>(); + List parts = new ArrayList<>(); + int prev = 0; boolean hadNullGuard = false; for (int i = 0; i < expression.length(); i++) { @@ -167,6 +170,7 @@ private static Object getNamedParameterValue(String expression, int namedParamet } } parts.add(new ExpressionPart(expression.substring(prev), hadNullGuard)); + for (ExpressionPart part : parts) { if (part.getName().isEmpty()) { throw new IllegalArgumentException("Expression `" + expression + "` is not formatted properly."); @@ -175,6 +179,7 @@ private static Object getNamedParameterValue(String expression, int namedParamet throw new IllegalArgumentException("Expression `" + expression + "` is not formatted properly."); } } + return parts.toArray(new ExpressionPart[0]); } @@ -228,15 +233,14 @@ private static Object getProperty(Object object, String property) { } private static final class ClassAndProperty { - private final Class clazz; + private final Class clazz; private final String property; - private ClassAndProperty(Class clazz, String property) { + private ClassAndProperty(Class clazz, String property) { this.clazz = clazz; this.property = property; } - @SuppressWarnings("RedundantIfStatement") @Override public boolean equals(Object o) { if (this == o) { diff --git a/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java b/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java index 9d5909e..d444158 100644 --- a/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java +++ b/code/src/test/java/com/codeforces/commons/reflection/MethodArgumentsFormatUtilTest.java @@ -25,6 +25,7 @@ public void testFormat() throws NoSuchMethodException { User userA = new User(123, true, new Country("Russia")); User userB = new User(-32, false, new Country("US")); User userC = null; + User userNoCountry = new User(0, false, null); Country countryA = new Country(""); Country countryB = new Country(null); @@ -44,6 +45,10 @@ public void testFormat() throws NoSuchMethodException { Assert.assertThrows(NullPointerException.class, () -> MethodArgumentsFormatUtil.format("countryNameLength=${country.name?.length}", method, new Object[] {userC, 32, 13, countryNull})); Assert.assertThrows(NullPointerException.class, () -> MethodArgumentsFormatUtil.format("countryNameLength=${country?.name.length}", method, new Object[] {userC, 32, 13, countryB})); + Assert.assertEquals("test=null", MethodArgumentsFormatUtil.format("test=${firstUser?.country}", method, new Object[] {userNoCountry, 1, 1, countryA})); + Assert.assertEquals("test=null", MethodArgumentsFormatUtil.format("test=${firstUser?.country}", method, new Object[] {null, 1, 1, countryA})); + Assert.assertEquals("test=null", MethodArgumentsFormatUtil.format("test=${firstUser.country?.name}", method, new Object[] {userNoCountry, 1, 1, countryA})); + Assert.assertEquals("Cache-user:123,true,Russia and null[32]", MethodArgumentsFormatUtil.format("Cache-user:${firstUser.id},${firstUser.male},${firstUser.country.name} and ${country.name}[${index}]", method, new Object[] {userA, 32, 13, countryB})); Assert.assertEquals("Cache-user:-32,false,US and Страна[-1]", MethodArgumentsFormatUtil.format("Cache-user:${firstUser.id},${firstUser.male},${firstUser.country.name} and ${country.name}[${index}]", method, new Object[] {userB, -1, -2, countryC})); Assert.assertEquals("Cache-user:123,true,6 and null[32]", MethodArgumentsFormatUtil.format("Cache-user:${firstUser.id},${firstUser.male},${firstUser.country.name.length} and ${country.name}[${index}]", method, new Object[] {userA, 32, 13, countryB}));