Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions code/bundle.bat
Original file line number Diff line number Diff line change
Expand Up @@ -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 %*
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -14,6 +13,8 @@
import java.lang.annotation.Annotation;
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;
Expand All @@ -36,6 +37,7 @@ public class MethodArgumentsFormatUtil {
.expireAfterWrite(1, TimeUnit.HOURS)
.build(
new CacheLoader<Method, Annotation[][]>() {
@SuppressWarnings("NullableProblems")
public Annotation[][] load(@NotNull Method method) {
return method.getParameterAnnotations();
}
Expand Down Expand Up @@ -86,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;
}
Expand All @@ -108,24 +111,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.");
Expand All @@ -136,27 +127,62 @@ 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) {
return "null";
} else {
throw new NullPointerException("Parameter `" + tokens[0] + "` is null and can't be used to get it's property.");
}
if (object == null && parts.length == 1) {
return "null";
}

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) {
List<ExpressionPart> 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);
Expand Down Expand Up @@ -207,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) {
Expand All @@ -239,5 +264,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;
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,30 @@ 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);
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}));
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.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("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}));
Expand Down
Loading