From dddac08308d049bb691fe493aa5423e53b3d6ab9 Mon Sep 17 00:00:00 2001 From: aschenzle Date: Thu, 23 Apr 2026 15:37:22 -0700 Subject: [PATCH 1/3] DynamoDB operation modeling and parsers. --- client-java/controller/pom.xml | 41 +- .../dynamodb/DynamoDbConditionExpression.g4 | 123 ++++++ .../DynamoDbAttributeValueHelper.java | 178 +++++++++ .../dynamodb/DynamoDbComparisonType.java | 56 +++ .../dynamodb/DynamoDbExpressionParser.java | 351 +++++++++++++++++ .../dynamodb/DynamoDbReflectionHelper.java | 29 ++ .../dynamodb/DynamoDbRequestParser.java | 62 +++ .../dynamodb/operations/AndOperation.java | 16 + .../operations/BeginsWithOperation.java | 20 + .../dynamodb/operations/BetweenOperation.java | 26 ++ .../operations/ContainsOperation.java | 20 + .../dynamodb/operations/ExistsOperation.java | 21 + .../dynamodb/operations/InOperation.java | 22 ++ .../dynamodb/operations/NotOperation.java | 14 + .../dynamodb/operations/OrOperation.java | 16 + .../dynamodb/operations/QueryOperation.java | 7 + .../dynamodb/operations/SizeOperation.java | 28 ++ .../dynamodb/operations/TypeOperation.java | 20 + .../comparison/ComparisonOperation.java | 22 ++ .../comparison/EqualsOperation.java | 8 + .../GreaterThanEqualsOperation.java | 8 + .../comparison/GreaterThanOperation.java | 8 + .../comparison/LessThanEqualsOperation.java | 8 + .../comparison/LessThanOperation.java | 8 + .../comparison/NotEqualsOperation.java | 8 + .../parsers/BatchGetItemApiMethodParser.java | 61 +++ .../parsers/DeleteItemApiMethodParser.java | 16 + .../parsers/DynamoDbApiMethodParser.java | 16 + .../parsers/DynamoDbBaseApiMethodParser.java | 116 ++++++ .../parsers/GetItemApiMethodParser.java | 21 + .../parsers/PutItemApiMethodParser.java | 16 + .../parsers/QueryApiMethodParser.java | 39 ++ .../dynamodb/parsers/ScanApiMethodParser.java | 31 ++ .../parsers/UpdateItemApiMethodParser.java | 16 + .../dynamodb/parsers/WriteMethodParser.java | 28 ++ .../DynamoDbAttributeValueHelperTest.java | 183 +++++++++ .../DynamoDbExpressionParserTest.java | 123 ++++++ .../dynamodb/DynamoDbRequestParserTest.java | 361 ++++++++++++++++++ .../controller/dynamodb/DynamoDbTestBase.java | 65 ++++ .../test/resources/simplelogger.properties | 1 + client-java/instrumentation/pom.xml | 3 +- .../java/instrumentation/DynamoDbCommand.java | 9 +- .../DynamoDbOperationNames.java | 30 ++ .../DynamoDbClassReplacement.java | 41 +- .../DynamoDbClassReplacementTest.java | 31 +- core-parent/pom.xml | 2 +- 46 files changed, 2279 insertions(+), 50 deletions(-) create mode 100644 client-java/controller/src/main/antlr4/org/evomaster/client/java/controller/dynamodb/DynamoDbConditionExpression.g4 create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelper.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbReflectionHelper.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParser.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/AndOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/OrOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/QueryOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/BatchGetItemApiMethodParser.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DeleteItemApiMethodParser.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbApiMethodParser.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbBaseApiMethodParser.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/GetItemApiMethodParser.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/PutItemApiMethodParser.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/QueryApiMethodParser.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/ScanApiMethodParser.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/UpdateItemApiMethodParser.java create mode 100644 client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/WriteMethodParser.java create mode 100644 client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelperTest.java create mode 100644 client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParserTest.java create mode 100644 client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParserTest.java create mode 100644 client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbTestBase.java create mode 100644 client-java/controller/src/test/resources/simplelogger.properties create mode 100644 client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/DynamoDbOperationNames.java diff --git a/client-java/controller/pom.xml b/client-java/controller/pom.xml index e599727ab4..93965fff82 100644 --- a/client-java/controller/pom.xml +++ b/client-java/controller/pom.xml @@ -140,6 +140,13 @@ provided + + + org.antlr + antlr4-runtime + + + com.h2database h2 @@ -204,10 +211,15 @@ lettuce-core test - software.amazon.awssdk - netty-nio-client + dynamodb + test + + + + org.slf4j + slf4j-simple test @@ -227,7 +239,6 @@ grpc-stub test - @@ -265,6 +276,23 @@ maven-compiler-plugin + + + org.antlr + antlr4-maven-plugin + + false + true + + + + + antlr4 + + + + + + + org.antlr + shaded.org.antlr + + diff --git a/client-java/controller/src/main/antlr4/org/evomaster/client/java/controller/dynamodb/DynamoDbConditionExpression.g4 b/client-java/controller/src/main/antlr4/org/evomaster/client/java/controller/dynamodb/DynamoDbConditionExpression.g4 new file mode 100644 index 0000000000..81b72a6d5c --- /dev/null +++ b/client-java/controller/src/main/antlr4/org/evomaster/client/java/controller/dynamodb/DynamoDbConditionExpression.g4 @@ -0,0 +1,123 @@ +grammar DynamoDbConditionExpression; + +expression + : orExpr EOF + ; + +orExpr + : andExpr (OR andExpr)* + ; + +andExpr + : notExpr (AND notExpr)* + ; + +notExpr + : NOT notExpr #negatedExpr + | primary #primaryExpr + ; + +primary + : LPAREN orExpr RPAREN #parenthesizedPrimary + | predicate #predicatePrimary + ; + +predicate + : ATTRIBUTE_EXISTS LPAREN path RPAREN #attributeExistsPredicate + | ATTRIBUTE_NOT_EXISTS LPAREN path RPAREN #attributeNotExistsPredicate + | ATTRIBUTE_TYPE LPAREN path COMMA value RPAREN #attributeTypePredicate + | BEGINS_WITH LPAREN path COMMA value RPAREN #beginsWithPredicate + | CONTAINS LPAREN path COMMA value RPAREN #containsPredicate + | SIZE LPAREN path RPAREN comparator value #sizePredicate + | path BETWEEN value AND value #betweenPredicate + | path IN LPAREN value (COMMA value)* RPAREN #inPredicate + | path comparator value #comparisonPredicate + ; + +comparator + : EQ + | NE + | LT + | LTE + | GT + | GTE + ; + +path + : IDENTIFIER + ; + +value + : PLACEHOLDER #placeholderValue + | STRING_LITERAL #stringValue + | NUMBER_LITERAL #numberValue + | BOOLEAN_LITERAL #booleanValue + | NULL_LITERAL #nullValue + | IDENTIFIER #identifierValue + ; + +LPAREN : '('; +RPAREN : ')'; +COMMA : ','; +EQ : '='; +NE : '<>'; +LTE : '<='; +GTE : '>='; +LT : '<'; +GT : '>'; + +AND : A N D; +OR : O R; +NOT : N O T; +BETWEEN : B E T W E E N; +IN : I N; +ATTRIBUTE_EXISTS : A T T R I B U T E '_' E X I S T S; +ATTRIBUTE_NOT_EXISTS : A T T R I B U T E '_' N O T '_' E X I S T S; +ATTRIBUTE_TYPE : A T T R I B U T E '_' T Y P E; +BEGINS_WITH : B E G I N S '_' W I T H; +CONTAINS : C O N T A I N S; +SIZE : S I Z E; + +BOOLEAN_LITERAL : T R U E | F A L S E; +NULL_LITERAL : N U L L; + +PLACEHOLDER : ':' IDENT_START IDENT_PART*; +NUMBER_LITERAL : '-'? DIGIT+ ('.' DIGIT+)? EXPONENT?; +STRING_LITERAL : '\'' ~['\r\n]* '\''; + +IDENTIFIER : IDENT_START IDENT_PART* INDEX* ('.' IDENT_START IDENT_PART* INDEX*)*; + +WS : [ \t\r\n]+ -> skip; + +fragment INDEX : '[' DIGIT+ ']'; +fragment EXPONENT : [eE] [+\-]? DIGIT+; +fragment IDENT_START : [a-zA-Z_#]; +fragment IDENT_PART : [a-zA-Z0-9_]; +fragment DIGIT : [0-9]; + +fragment A : [aA]; +fragment B : [bB]; +fragment C : [cC]; +fragment D : [dD]; +fragment E : [eE]; +fragment F : [fF]; +fragment G : [gG]; +fragment H : [hH]; +fragment I : [iI]; +fragment J : [jJ]; +fragment K : [kK]; +fragment L : [lL]; +fragment M : [mM]; +fragment N : [nN]; +fragment O : [oO]; +fragment P : [pP]; +fragment Q : [qQ]; +fragment R : [rR]; +fragment S : [sS]; +fragment T : [tT]; +fragment U : [uU]; +fragment V : [vV]; +fragment W : [wW]; +fragment X : [xX]; +fragment Y : [yY]; +fragment Z : [zZ]; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelper.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelper.java new file mode 100644 index 0000000000..d55b85444c --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelper.java @@ -0,0 +1,178 @@ +package org.evomaster.client.java.controller.dynamodb; + +import java.nio.ByteBuffer; +import java.util.*; + +/** + * Utilities to deal with DynamoDB SDK request/response values without + * introducing direct compile-time dependencies to AWS SDK classes. + */ +public final class DynamoDbAttributeValueHelper { + + /** + * Reflection-bound AWS AttributeValue accessors. Keep these literals unchanged: + * they must match SDK method names exactly. + */ + private static final String METHOD_NUL = "nul"; + private static final String METHOD_S = "s"; + private static final String METHOD_N = "n"; + private static final String METHOD_BOOL = "bool"; + private static final String METHOD_HAS_M = "hasM"; + private static final String METHOD_M = "m"; + private static final String METHOD_HAS_L = "hasL"; + private static final String METHOD_L = "l"; + private static final String METHOD_HAS_SS = "hasSs"; + private static final String METHOD_SS = "ss"; + private static final String METHOD_HAS_NS = "hasNs"; + private static final String METHOD_NS = "ns"; + private static final String METHOD_HAS_BS = "hasBs"; + private static final String METHOD_BS = "bs"; + private static final String METHOD_B = "b"; + + private static final String DECIMAL_SEPARATOR = "."; + private static final String SCIENTIFIC_NOTATION_E_LOWER = "e"; + private static final String SCIENTIFIC_NOTATION_E_UPPER = "E"; + + private DynamoDbAttributeValueHelper() { + } + + public static Map toPlainMap(Object source) { + if (!(source instanceof Map)) { + return Collections.emptyMap(); + } + + Map result = new LinkedHashMap<>(); + ((Map) source).forEach((key, value) -> { + if (key != null) { + result.put(String.valueOf(key), toPlainValue(value)); + } + }); + return result; + } + + @SuppressWarnings("unchecked") + public static Object toPlainValue(Object value) { + if (value == null) { + return null; + } + + if (value instanceof Map) { + return toPlainMap(value); + } + + if (value instanceof Collection) { + return toPlainList((Collection) value); + } + + // The AWS SDK AttributeValue class exposes "hasXxx"/"xxx" methods. + // We use reflection to stay decoupled from specific SDK versions. + Object nul = DynamoDbReflectionHelper.invokeBooleanNoArg(value, METHOD_NUL); + if (Boolean.TRUE.equals(nul)) { + return null; + } + + Object s = DynamoDbReflectionHelper.invokeNoArg(value, METHOD_S); + if (s instanceof String) { + return s; + } + + Object n = DynamoDbReflectionHelper.invokeNoArg(value, METHOD_N); + if (n instanceof String && !((String) n).isEmpty()) { + return parseNumber((String) n); + } + + Object bool = DynamoDbReflectionHelper.invokeNoArg(value, METHOD_BOOL); + if (bool instanceof Boolean) { + return bool; + } + + Object m = readIfPresent(value, METHOD_HAS_M, METHOD_M); + if (m instanceof Map) { + return toPlainMap(m); + } + + Object l = readIfPresent(value, METHOD_HAS_L, METHOD_L); + if (l instanceof Collection) { + return toPlainList((Collection) l); + } + + Object ss = readIfPresent(value, METHOD_HAS_SS, METHOD_SS); + if (ss instanceof Collection) { + return new LinkedHashSet<>((Collection) ss); + } + + Object ns = readIfPresent(value, METHOD_HAS_NS, METHOD_NS); + if (ns instanceof Collection) { + return toNumberSet((Collection) ns); + } + + Object bs = readIfPresent(value, METHOD_HAS_BS, METHOD_BS); + if (bs instanceof Collection) { + return toBinarySet((Collection) bs); + } + + Object b = DynamoDbReflectionHelper.invokeNoArg(value, METHOD_B); + if (b != null) { + return toPlainBinary(b); + } + + return value; + } + + private static Object toPlainBinary(Object value) { + if (value instanceof ByteBuffer) { + ByteBuffer bb = ((ByteBuffer) value).asReadOnlyBuffer(); + byte[] bytes = new byte[bb.remaining()]; + bb.get(bytes); + return bytes; + } + + return value; + } + + private static Object readIfPresent(Object target, String hasMethod, String valueMethod) { + if (Boolean.TRUE.equals(DynamoDbReflectionHelper.invokeBooleanNoArg(target, hasMethod))) { + return DynamoDbReflectionHelper.invokeNoArg(target, valueMethod); + } + return null; + } + + private static List toPlainList(Collection source) { + List converted = new ArrayList<>(source.size()); + for (Object element : source) { + converted.add(toPlainValue(element)); + } + return converted; + } + + private static Set toNumberSet(Collection source) { + LinkedHashSet numbers = new LinkedHashSet<>(); + for (Object number : source) { + if (number != null) { + numbers.add(parseNumber(String.valueOf(number))); + } + } + return numbers; + } + + private static Set toBinarySet(Collection source) { + LinkedHashSet binaries = new LinkedHashSet<>(); + for (Object binary : source) { + binaries.add(toPlainBinary(binary)); + } + return binaries; + } + + private static Object parseNumber(String text) { + try { + if (text.contains(DECIMAL_SEPARATOR) + || text.contains(SCIENTIFIC_NOTATION_E_LOWER) + || text.contains(SCIENTIFIC_NOTATION_E_UPPER)) { + return Double.parseDouble(text); + } + return Long.parseLong(text); + } catch (NumberFormatException e) { + return Double.NaN; + } + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java new file mode 100644 index 0000000000..9acc303af5 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java @@ -0,0 +1,56 @@ +package org.evomaster.client.java.controller.dynamodb; + +import org.evomaster.client.java.controller.dynamodb.operations.comparison.*; + +/** + * Shared normalized comparison types used by DynamoDB parsers. + */ +public enum DynamoDbComparisonType { + EQUALS, + NOT_EQUALS, + GREATER_THAN, + GREATER_THAN_EQUALS, + LESS_THAN, + LESS_THAN_EQUALS; + + public static DynamoDbComparisonType fromToken(String token) { + if ("=".equals(token)) { + return EQUALS; + } + if ("<>".equals(token)) { + return NOT_EQUALS; + } + if (">".equals(token)) { + return GREATER_THAN; + } + if (">=".equals(token)) { + return GREATER_THAN_EQUALS; + } + if ("<".equals(token)) { + return LESS_THAN; + } + if ("<=".equals(token)) { + return LESS_THAN_EQUALS; + } + throw new IllegalArgumentException("Unsupported comparator token: " + token); + } + + public ComparisonOperation toOperation(String fieldName, Object value) { + switch (this) { + case EQUALS: + return new EqualsOperation<>(fieldName, value); + case NOT_EQUALS: + return new NotEqualsOperation<>(fieldName, value); + case GREATER_THAN: + return new GreaterThanOperation<>(fieldName, value); + case GREATER_THAN_EQUALS: + return new GreaterThanEqualsOperation<>(fieldName, value); + case LESS_THAN: + return new LessThanOperation<>(fieldName, value); + case LESS_THAN_EQUALS: + return new LessThanEqualsOperation<>(fieldName, value); + default: + throw new IllegalStateException("Unsupported comparator: " + this); + } + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java new file mode 100644 index 0000000000..c1b67c4712 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java @@ -0,0 +1,351 @@ +package org.evomaster.client.java.controller.dynamodb; + +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.ParseCancellationException; +import org.evomaster.client.java.controller.dynamodb.operations.*; + +import java.util.*; + +/** + * Parser for DynamoDB key/filter/condition expression strings. + * Supported operators/functions align with DynamoDB expression docs: + * =, <>, <, <=, >, >=, BETWEEN, IN, AND, OR, NOT, + * attribute_exists, attribute_not_exists, attribute_type, + * begins_with, contains, size. + */ +public class DynamoDbExpressionParser { + + private Map expressionAttributeNames = Collections.emptyMap(); + private Map expressionAttributeValues = Collections.emptyMap(); + + /** + * Parses a DynamoDB expression and converts it into a query-operation tree. + * + * @param expression the DynamoDB expression string to parse + * @param expressionAttributeNames optional map of attribute-name aliases + * @param expressionAttributeValues optional map of value placeholders + * @return the parsed operation tree, or {@code null} when the expression is blank + */ + public QueryOperation parse( + String expression, + Map expressionAttributeNames, + Map expressionAttributeValues) { + if (expression == null || expression.trim().isEmpty()) { + return null; + } + + this.expressionAttributeNames = expressionAttributeNames == null + ? Collections.emptyMap() + : expressionAttributeNames; + this.expressionAttributeValues = expressionAttributeValues == null + ? Collections.emptyMap() + : expressionAttributeValues; + + try { + DynamoDbConditionExpressionLexer lexer = new DynamoDbConditionExpressionLexer(CharStreams.fromString(expression)); + prepareLexer(lexer); + + CommonTokenStream tokenStream = new CommonTokenStream(lexer); + DynamoDbConditionExpressionParser parser = new DynamoDbConditionExpressionParser(tokenStream); + prepareParser(parser); + + DynamoDbConditionExpressionParser.ExpressionContext tree = parser.expression(); + return new OperationVisitor().visit(tree); + } catch (ParseCancellationException e) { + throw new IllegalArgumentException("Invalid DynamoDB expression: " + expression, e); + } + } + + /** + * Configures lexer error handling to fail fast on invalid input. + * + * @param lexer the lexer to configure + */ + private void prepareLexer(Lexer lexer) { + lexer.removeErrorListeners(); + lexer.addErrorListener(ThrowingErrorListener.INSTANCE); + } + + /** + * Configures parser error handling to fail fast on invalid input. + * + * @param parser the parser to configure + */ + private void prepareParser(Parser parser) { + parser.removeErrorListeners(); + parser.addErrorListener(ThrowingErrorListener.INSTANCE); + } + + /** + * Parses and resolves a field path from Parser context. + * + * @param pathContext the parsed path context + * @return resolved field path with attribute-name aliases expanded + */ + private String parsePath(DynamoDbConditionExpressionParser.PathContext pathContext) { + return parseFieldName(pathContext.getText()); + } + + /** + * Resolves expression-attribute-name aliases in a dotted field path. + * + * @param token raw field token from the expression + * @return resolved field path + */ + private String parseFieldName(String token) { + String[] chunks = token.split("\\."); + List resolved = new ArrayList<>(chunks.length); + for (String chunk : chunks) { + int bracket = chunk.indexOf('['); + String base = bracket >= 0 ? chunk.substring(0, bracket) : chunk; + String suffix = bracket >= 0 ? chunk.substring(bracket) : ""; + if (base.startsWith("#")) { + resolved.add(expressionAttributeNames.getOrDefault(base, base) + suffix); + } else { + resolved.add(base + suffix); + } + } + return String.join(".", resolved); + } + + /** + * Converts a parsed value node into a runtime Java value. + * + * @param valueContext parsed value context + * @return converted Java value + */ + private Object parseValue(DynamoDbConditionExpressionParser.ValueContext valueContext) { + if (valueContext == null) { + return null; + } + return new ValueVisitor().visit(valueContext); + } + + /** + * Parses a numeric literal into {@link Long} or {@link Double}. + * + * @param token numeric literal token + * @return parsed number, or original token when parsing fails + */ + private Object parseNumberLiteral(String token) { + try { + if (token.contains(".") || token.toLowerCase(Locale.ROOT).contains("e")) { + return Double.parseDouble(token); + } + return Long.parseLong(token); + } catch (NumberFormatException ignored) { + // Keep unknown numeric-like literals as-is. + return token; + } + } + + /** + * Combines two operations with logical AND, flattening nested AND nodes. + * + * @param left left condition + * @param right right condition + * @return merged AND operation + */ + private QueryOperation mergeAnd(QueryOperation left, QueryOperation right) { + if (left instanceof AndOperation) { + List conditions = new ArrayList<>(((AndOperation) left).getConditions()); + conditions.add(right); + return new AndOperation(conditions); + } + return new AndOperation(Arrays.asList(left, right)); + } + + /** + * Combines two operations with logical OR, flattening nested OR nodes. + * + * @param left left condition + * @param right right condition + * @return merged OR operation + */ + private QueryOperation mergeOr(QueryOperation left, QueryOperation right) { + if (left instanceof OrOperation) { + List conditions = new ArrayList<>(((OrOperation) left).getConditions()); + conditions.add(right); + return new OrOperation(conditions); + } + return new OrOperation(Arrays.asList(left, right)); + } + + private class ValueVisitor extends DynamoDbConditionExpressionBaseVisitor { + + /** {@inheritDoc} */ + @Override + public Object visitPlaceholderValue(DynamoDbConditionExpressionParser.PlaceholderValueContext ctx) { + return expressionAttributeValues.get(ctx.PLACEHOLDER().getText()); + } + + /** {@inheritDoc} */ + @Override + public Object visitStringValue(DynamoDbConditionExpressionParser.StringValueContext ctx) { + String token = ctx.STRING_LITERAL().getText(); + return token.substring(1, token.length() - 1); + } + + /** {@inheritDoc} */ + @Override + public Object visitNumberValue(DynamoDbConditionExpressionParser.NumberValueContext ctx) { + return parseNumberLiteral(ctx.NUMBER_LITERAL().getText()); + } + + /** {@inheritDoc} */ + @Override + public Object visitBooleanValue(DynamoDbConditionExpressionParser.BooleanValueContext ctx) { + return "TRUE".equalsIgnoreCase(ctx.BOOLEAN_LITERAL().getText()); + } + + /** {@inheritDoc} */ + @Override + public Object visitNullValue(DynamoDbConditionExpressionParser.NullValueContext ctx) { + return null; + } + + /** {@inheritDoc} */ + @Override + public Object visitIdentifierValue(DynamoDbConditionExpressionParser.IdentifierValueContext ctx) { + return ctx.IDENTIFIER().getText(); + } + } + + private class OperationVisitor extends DynamoDbConditionExpressionBaseVisitor { + + /** {@inheritDoc} */ + @Override + public QueryOperation visitExpression(DynamoDbConditionExpressionParser.ExpressionContext ctx) { + return visit(ctx.orExpr()); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitOrExpr(DynamoDbConditionExpressionParser.OrExprContext ctx) { + QueryOperation left = visit(ctx.andExpr(0)); + for (int i = 1; i < ctx.andExpr().size(); i++) { + QueryOperation right = visit(ctx.andExpr(i)); + left = mergeOr(left, right); + } + return left; + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitAndExpr(DynamoDbConditionExpressionParser.AndExprContext ctx) { + QueryOperation left = visit(ctx.notExpr(0)); + for (int i = 1; i < ctx.notExpr().size(); i++) { + QueryOperation right = visit(ctx.notExpr(i)); + left = mergeAnd(left, right); + } + return left; + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitNegatedExpr(DynamoDbConditionExpressionParser.NegatedExprContext ctx) { + return new NotOperation(visit(ctx.notExpr())); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitPrimaryExpr(DynamoDbConditionExpressionParser.PrimaryExprContext ctx) { + return visit(ctx.primary()); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitParenthesizedPrimary(DynamoDbConditionExpressionParser.ParenthesizedPrimaryContext ctx) { + return visit(ctx.orExpr()); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitPredicatePrimary(DynamoDbConditionExpressionParser.PredicatePrimaryContext ctx) { + return visit(ctx.predicate()); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitAttributeExistsPredicate(DynamoDbConditionExpressionParser.AttributeExistsPredicateContext ctx) { + return new ExistsOperation(parsePath(ctx.path()), true); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitAttributeNotExistsPredicate(DynamoDbConditionExpressionParser.AttributeNotExistsPredicateContext ctx) { + return new ExistsOperation(parsePath(ctx.path()), false); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitAttributeTypePredicate(DynamoDbConditionExpressionParser.AttributeTypePredicateContext ctx) { + Object expectedType = parseValue(ctx.value()); + return new TypeOperation(parsePath(ctx.path()), expectedType == null ? null : String.valueOf(expectedType)); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitBeginsWithPredicate(DynamoDbConditionExpressionParser.BeginsWithPredicateContext ctx) { + return new BeginsWithOperation(parsePath(ctx.path()), parseValue(ctx.value())); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitContainsPredicate(DynamoDbConditionExpressionParser.ContainsPredicateContext ctx) { + return new ContainsOperation(parsePath(ctx.path()), parseValue(ctx.value())); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitSizePredicate(DynamoDbConditionExpressionParser.SizePredicateContext ctx) { + String field = parsePath(ctx.path()); + DynamoDbComparisonType comparator = DynamoDbComparisonType.fromToken(ctx.comparator().getText()); + Object expectedValue = parseValue(ctx.value()); + return new SizeOperation(field, comparator, expectedValue); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitBetweenPredicate(DynamoDbConditionExpressionParser.BetweenPredicateContext ctx) { + String field = parsePath(ctx.path()); + Object lower = parseValue(ctx.value(0)); + Object upper = parseValue(ctx.value(1)); + return new BetweenOperation(field, lower, upper); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitInPredicate(DynamoDbConditionExpressionParser.InPredicateContext ctx) { + List values = new ArrayList<>(); + for (DynamoDbConditionExpressionParser.ValueContext valueContext : ctx.value()) { + values.add(parseValue(valueContext)); + } + return new InOperation<>(parsePath(ctx.path()), values); + } + + /** {@inheritDoc} */ + @Override + public QueryOperation visitComparisonPredicate(DynamoDbConditionExpressionParser.ComparisonPredicateContext ctx) { + String field = parsePath(ctx.path()); + DynamoDbComparisonType comparator = DynamoDbComparisonType.fromToken(ctx.comparator().getText()); + Object value = parseValue(ctx.value()); + return comparator.toOperation(field, value); + } + } + + /** + * see ... + */ + private static class ThrowingErrorListener extends BaseErrorListener { + + private static final ThrowingErrorListener INSTANCE = new ThrowingErrorListener(); + + /** {@inheritDoc} */ + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, + int charPositionInLine, String msg, RecognitionException e) { + throw new ParseCancellationException("line " + line + ":" + charPositionInLine + " " + msg); + } + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbReflectionHelper.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbReflectionHelper.java new file mode 100644 index 0000000000..1a2e8792b0 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbReflectionHelper.java @@ -0,0 +1,29 @@ +package org.evomaster.client.java.controller.dynamodb; + +import java.lang.reflect.Method; + +/** + * Shared reflection helpers used by DynamoDB parser utilities. + */ +public final class DynamoDbReflectionHelper { + + private DynamoDbReflectionHelper() { + } + + public static Object invokeNoArg(Object target, String methodName) { + if (target == null) { + return null; + } + try { + Method method = target.getClass().getMethod(methodName); + return method.invoke(target); + } catch (Exception ignored) { + return null; + } + } + + public static Boolean invokeBooleanNoArg(Object target, String methodName) { + Object value = invokeNoArg(target, methodName); + return value instanceof Boolean ? (Boolean) value : null; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParser.java new file mode 100644 index 0000000000..67004beb9d --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParser.java @@ -0,0 +1,62 @@ +package org.evomaster.client.java.controller.dynamodb; + +import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; +import org.evomaster.client.java.controller.dynamodb.parsers.*; +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Builds internal operations from DynamoDB request objects. + */ +public class DynamoDbRequestParser { + + private final Map parsersByApiMethod; + + public DynamoDbRequestParser() { + Map map = new LinkedHashMap<>(); + registerParser(map, new QueryApiMethodParser()); + registerParser(map, new ScanApiMethodParser()); + registerParser(map, new GetItemApiMethodParser()); + registerParser(map, new BatchGetItemApiMethodParser()); + registerParser(map, new PutItemApiMethodParser()); + registerParser(map, new DeleteItemApiMethodParser()); + registerParser(map, new UpdateItemApiMethodParser()); + this.parsersByApiMethod = Collections.unmodifiableMap(map); + } + + /** + * Entry-point parser used by the handler. + * It routes a DynamoDB SDK request to the API-method parser and returns + * one parsed condition tree per table name. + * Unsupported operations intentionally yield an empty map. + */ + public Map parseByTable(Object request, DynamoDbOperationNames apiMethodName) { + if (request == null || apiMethodName == null) { + return Collections.emptyMap(); + } + + DynamoDbApiMethodParser parser = parsersByApiMethod.get(apiMethodName); + if (parser == null) { + return Collections.emptyMap(); + } + + Map parsed = parser.parseRequest(request); + return parsed == null ? Collections.emptyMap() : parsed; + } + + private static void registerParser(Map parsersByApiMethod, + DynamoDbApiMethodParser parser) { + DynamoDbOperationNames apiMethodName = parser.apiMethodName(); + if (apiMethodName == null) { + throw new IllegalArgumentException("Parser api method name cannot be null or blank"); + } + + DynamoDbApiMethodParser previous = parsersByApiMethod.put(apiMethodName, parser); + if (previous != null) { + throw new IllegalStateException("Duplicate parser for api method " + apiMethodName); + } + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/AndOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/AndOperation.java new file mode 100644 index 0000000000..f1794713da --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/AndOperation.java @@ -0,0 +1,16 @@ +package org.evomaster.client.java.controller.dynamodb.operations; + +import java.util.List; + +public class AndOperation extends QueryOperation { + + private final List conditions; + + public AndOperation(List conditions) { + this.conditions = conditions; + } + + public List getConditions() { + return conditions; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java new file mode 100644 index 0000000000..043d6fa7de --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java @@ -0,0 +1,20 @@ +package org.evomaster.client.java.controller.dynamodb.operations; + +public class BeginsWithOperation extends QueryOperation { + + private final String fieldName; + private final Object prefix; + + public BeginsWithOperation(String fieldName, Object prefix) { + this.fieldName = fieldName; + this.prefix = prefix; + } + + public String getFieldName() { + return fieldName; + } + + public Object getPrefix() { + return prefix; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java new file mode 100644 index 0000000000..839db62eca --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java @@ -0,0 +1,26 @@ +package org.evomaster.client.java.controller.dynamodb.operations; + +public class BetweenOperation extends QueryOperation { + + private final String fieldName; + private final Object lowerBound; + private final Object upperBound; + + public BetweenOperation(String fieldName, Object lowerBound, Object upperBound) { + this.fieldName = fieldName; + this.lowerBound = lowerBound; + this.upperBound = upperBound; + } + + public String getFieldName() { + return fieldName; + } + + public Object getLowerBound() { + return lowerBound; + } + + public Object getUpperBound() { + return upperBound; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java new file mode 100644 index 0000000000..7bc35b5633 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java @@ -0,0 +1,20 @@ +package org.evomaster.client.java.controller.dynamodb.operations; + +public class ContainsOperation extends QueryOperation { + + private final String fieldName; + private final Object expectedValue; + + public ContainsOperation(String fieldName, Object expectedValue) { + this.fieldName = fieldName; + this.expectedValue = expectedValue; + } + + public String getFieldName() { + return fieldName; + } + + public Object getExpectedValue() { + return expectedValue; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java new file mode 100644 index 0000000000..33bf30a4f8 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java @@ -0,0 +1,21 @@ +package org.evomaster.client.java.controller.dynamodb.operations; + +public class ExistsOperation extends QueryOperation { + + private final String fieldName; + //true = exists, false = not exists + private final boolean exists; + + public ExistsOperation(String fieldName, boolean exists) { + this.fieldName = fieldName; + this.exists = exists; + } + + public String getFieldName() { + return fieldName; + } + + public boolean isExists() { + return exists; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java new file mode 100644 index 0000000000..3b3f444f2b --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java @@ -0,0 +1,22 @@ +package org.evomaster.client.java.controller.dynamodb.operations; + +import java.util.List; + +public class InOperation extends QueryOperation { + + private final String fieldName; + private final List values; + + public InOperation(String fieldName, List values) { + this.fieldName = fieldName; + this.values = values; + } + + public String getFieldName() { + return fieldName; + } + + public List getValues() { + return values; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java new file mode 100644 index 0000000000..075a6060a1 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java @@ -0,0 +1,14 @@ +package org.evomaster.client.java.controller.dynamodb.operations; + +public class NotOperation extends QueryOperation { + + private final QueryOperation condition; + + public NotOperation(QueryOperation condition) { + this.condition = condition; + } + + public QueryOperation getCondition() { + return condition; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/OrOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/OrOperation.java new file mode 100644 index 0000000000..de66cdc773 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/OrOperation.java @@ -0,0 +1,16 @@ +package org.evomaster.client.java.controller.dynamodb.operations; + +import java.util.List; + +public class OrOperation extends QueryOperation { + + private final List conditions; + + public OrOperation(List conditions) { + this.conditions = conditions; + } + + public List getConditions() { + return conditions; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/QueryOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/QueryOperation.java new file mode 100644 index 0000000000..3748bb1f16 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/QueryOperation.java @@ -0,0 +1,7 @@ +package org.evomaster.client.java.controller.dynamodb.operations; + +/** + * Represents a DynamoDB condition/filter expression operation. + */ +public abstract class QueryOperation { +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java new file mode 100644 index 0000000000..a184d19695 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java @@ -0,0 +1,28 @@ +package org.evomaster.client.java.controller.dynamodb.operations; + +import org.evomaster.client.java.controller.dynamodb.DynamoDbComparisonType; + +public class SizeOperation extends QueryOperation { + + private final String fieldName; + private final DynamoDbComparisonType comparator; + private final Object expectedValue; + + public SizeOperation(String fieldName, DynamoDbComparisonType comparator, Object expectedValue) { + this.fieldName = fieldName; + this.comparator = comparator; + this.expectedValue = expectedValue; + } + + public String getFieldName() { + return fieldName; + } + + public DynamoDbComparisonType getComparator() { + return comparator; + } + + public Object getExpectedValue() { + return expectedValue; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java new file mode 100644 index 0000000000..fa1210de0f --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java @@ -0,0 +1,20 @@ +package org.evomaster.client.java.controller.dynamodb.operations; + +public class TypeOperation extends QueryOperation { + + private final String fieldName; + private final String expectedType; + + public TypeOperation(String fieldName, String expectedType) { + this.fieldName = fieldName; + this.expectedType = expectedType; + } + + public String getFieldName() { + return fieldName; + } + + public String getExpectedType() { + return expectedType; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java new file mode 100644 index 0000000000..da752a7442 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java @@ -0,0 +1,22 @@ +package org.evomaster.client.java.controller.dynamodb.operations.comparison; + +import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; + +public abstract class ComparisonOperation extends QueryOperation { + + private final String fieldName; + private final V value; + + ComparisonOperation(String fieldName, V value) { + this.fieldName = fieldName; + this.value = value; + } + + public String getFieldName() { + return fieldName; + } + + public V getValue() { + return value; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java new file mode 100644 index 0000000000..785cb179d8 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java @@ -0,0 +1,8 @@ +package org.evomaster.client.java.controller.dynamodb.operations.comparison; + +public class EqualsOperation extends ComparisonOperation { + + public EqualsOperation(String fieldName, V value) { + super(fieldName, value); + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java new file mode 100644 index 0000000000..c0fb797809 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java @@ -0,0 +1,8 @@ +package org.evomaster.client.java.controller.dynamodb.operations.comparison; + +public class GreaterThanEqualsOperation extends ComparisonOperation { + + public GreaterThanEqualsOperation(String fieldName, V value) { + super(fieldName, value); + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java new file mode 100644 index 0000000000..dc3ca3d79e --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java @@ -0,0 +1,8 @@ +package org.evomaster.client.java.controller.dynamodb.operations.comparison; + +public class GreaterThanOperation extends ComparisonOperation { + + public GreaterThanOperation(String fieldName, V value) { + super(fieldName, value); + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java new file mode 100644 index 0000000000..ee1bacc5e9 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java @@ -0,0 +1,8 @@ +package org.evomaster.client.java.controller.dynamodb.operations.comparison; + +public class LessThanEqualsOperation extends ComparisonOperation { + + public LessThanEqualsOperation(String fieldName, V value) { + super(fieldName, value); + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java new file mode 100644 index 0000000000..8e2db85375 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java @@ -0,0 +1,8 @@ +package org.evomaster.client.java.controller.dynamodb.operations.comparison; + +public class LessThanOperation extends ComparisonOperation { + + public LessThanOperation(String fieldName, V value) { + super(fieldName, value); + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java new file mode 100644 index 0000000000..b2a5e85312 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java @@ -0,0 +1,8 @@ +package org.evomaster.client.java.controller.dynamodb.operations.comparison; + +public class NotEqualsOperation extends ComparisonOperation { + + public NotEqualsOperation(String fieldName, V value) { + super(fieldName, value); + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/BatchGetItemApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/BatchGetItemApiMethodParser.java new file mode 100644 index 0000000000..a978665848 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/BatchGetItemApiMethodParser.java @@ -0,0 +1,61 @@ +package org.evomaster.client.java.controller.dynamodb.parsers; + +import org.evomaster.client.java.controller.dynamodb.DynamoDbAttributeValueHelper; +import org.evomaster.client.java.controller.dynamodb.operations.OrOperation; +import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; + +import java.util.*; + +import static org.evomaster.client.java.controller.dynamodb.DynamoDbReflectionHelper.invokeNoArg; + +public class BatchGetItemApiMethodParser extends DynamoDbBaseApiMethodParser { + + @Override + public DynamoDbOperationNames apiMethodName() { + return DynamoDbOperationNames.BATCH_GET_ITEM; + } + + @Override + @SuppressWarnings("unchecked") + public Map parseRequest(Object request) { + Object requestItemsObj = invokeNoArg(request, METHOD_REQUEST_ITEMS); + if (!(requestItemsObj instanceof Map)) { + return Collections.emptyMap(); + } + + Map result = new LinkedHashMap<>(); + Map requestItems = (Map) requestItemsObj; + for (Map.Entry entry : requestItems.entrySet()) { + String tableName = entry.getKey() == null ? null : String.valueOf(entry.getKey()); + if (tableName == null || tableName.trim().isEmpty()) { + continue; + } + + Object keysAndAttributes = entry.getValue(); + Object keysObj = invokeNoArg(keysAndAttributes, METHOD_KEYS); + if (!(keysObj instanceof Collection)) { + continue; + } + + List keyConditions = new ArrayList<>(); + for (Object rawKey : (Collection) keysObj) { + QueryOperation keyCondition = buildEqualsFromMap(DynamoDbAttributeValueHelper.toPlainMap(rawKey)); + if (keyCondition != null) { + keyConditions.add(keyCondition); + } + } + + QueryOperation tableOperation = combineWithOr(keyConditions); + if (tableOperation != null) { + result.put(tableName, tableOperation); + } + } + + return result; + } + + private QueryOperation combineWithOr(List conditions) { + return combine(conditions, OrOperation::new); + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DeleteItemApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DeleteItemApiMethodParser.java new file mode 100644 index 0000000000..ef3d2006a5 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DeleteItemApiMethodParser.java @@ -0,0 +1,16 @@ +package org.evomaster.client.java.controller.dynamodb.parsers; + +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; + +public class DeleteItemApiMethodParser extends WriteMethodParser { + + @Override + public DynamoDbOperationNames apiMethodName() { + return DynamoDbOperationNames.DELETE_ITEM; + } + + @Override + protected boolean requiresKeyCondition() { + return true; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbApiMethodParser.java new file mode 100644 index 0000000000..ea2ef5e821 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbApiMethodParser.java @@ -0,0 +1,16 @@ +package org.evomaster.client.java.controller.dynamodb.parsers; + +import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; + +import java.util.Map; + +/** + * Parser for one DynamoDB API method family. + */ +public interface DynamoDbApiMethodParser { + + DynamoDbOperationNames apiMethodName(); + + Map parseRequest(Object request); +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbBaseApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbBaseApiMethodParser.java new file mode 100644 index 0000000000..91f206dc06 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbBaseApiMethodParser.java @@ -0,0 +1,116 @@ +package org.evomaster.client.java.controller.dynamodb.parsers; + +import org.evomaster.client.java.controller.dynamodb.DynamoDbAttributeValueHelper; +import org.evomaster.client.java.controller.dynamodb.DynamoDbExpressionParser; +import org.evomaster.client.java.controller.dynamodb.operations.AndOperation; +import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; +import org.evomaster.client.java.controller.dynamodb.operations.comparison.EqualsOperation; + +import java.util.*; +import java.util.function.Function; + +import static org.evomaster.client.java.controller.dynamodb.DynamoDbReflectionHelper.invokeNoArg; + +abstract class DynamoDbBaseApiMethodParser implements DynamoDbApiMethodParser { + + // All these constants are used to invoke DDB API methods by reflection, do not change. + protected static final String METHOD_TABLE_NAME = "tableName"; + protected static final String METHOD_KEY_CONDITION_EXPRESSION = "keyConditionExpression"; + protected static final String METHOD_FILTER_EXPRESSION = "filterExpression"; + protected static final String METHOD_KEY = "key"; + protected static final String METHOD_REQUEST_ITEMS = "requestItems"; + protected static final String METHOD_KEYS = "keys"; + protected static final String METHOD_CONDITION_EXPRESSION = "conditionExpression"; + protected static final String METHOD_EXPRESSION_ATTRIBUTE_NAMES = "expressionAttributeNames"; + protected static final String METHOD_EXPRESSION_ATTRIBUTE_VALUES = "expressionAttributeValues"; + + protected QueryOperation parseExpression( + String expression, + Map expressionAttributeNames, + Map expressionAttributeValues) { + return new DynamoDbExpressionParser().parse(expression, expressionAttributeNames, expressionAttributeValues); + } + + protected QueryOperation parseKeyCondition(Object request) { + Object keyObj = invokeNoArg(request, METHOD_KEY); + return buildEqualsFromMap(DynamoDbAttributeValueHelper.toPlainMap(keyObj)); + } + + protected QueryOperation buildEqualsFromMap(Map values) { + if (values == null || values.isEmpty()) { + return null; + } + + List conditions = new ArrayList<>(); + values.forEach((key, value) -> conditions.add(new EqualsOperation<>(key, value))); + return combineWithAnd(conditions); + } + + protected QueryOperation combineWithAnd(QueryOperation left, QueryOperation right) { + return combineWithAnd(Arrays.asList(left, right)); + } + + protected QueryOperation combineWithAnd(List conditions) { + return combine(conditions, AndOperation::new); + } + + protected QueryOperation combine(List conditions, Function, QueryOperation> compositeBuilder) { + List filtered = new ArrayList<>(); + for (QueryOperation operation : conditions) { + if (operation != null) { + filtered.add(operation); + } + } + + if (filtered.isEmpty()) { + return null; + } + if (filtered.size() == 1) { + return filtered.get(0); + } + return compositeBuilder.apply(filtered); + } + + protected Map readNameMap(Object request) { + Object raw = invokeNoArg(request, METHOD_EXPRESSION_ATTRIBUTE_NAMES); + if (!(raw instanceof Map)) { + return Collections.emptyMap(); + } + + Map result = new LinkedHashMap<>(); + ((Map) raw).forEach((k, v) -> { + if (k != null && v != null) { + result.put(String.valueOf(k), String.valueOf(v)); + } + }); + return result; + } + + protected Map readValueMap(Object request) { + Object raw = invokeNoArg(request, METHOD_EXPRESSION_ATTRIBUTE_VALUES); + return DynamoDbAttributeValueHelper.toPlainMap(raw); + } + + protected String readString(Object target, String methodName) { + Object value = invokeNoArg(target, methodName); + return value == null ? null : String.valueOf(value); + } + + protected String readValidTableName(Object request) { + String tableName = readString(request, METHOD_TABLE_NAME); + if (tableName == null || tableName.trim().isEmpty()) { + return null; + } + return tableName; + } + + protected Map singleTableResult(String tableName, QueryOperation operation) { + if (tableName == null || operation == null) { + return Collections.emptyMap(); + } + + Map result = new LinkedHashMap<>(); + result.put(tableName, operation); + return result; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/GetItemApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/GetItemApiMethodParser.java new file mode 100644 index 0000000000..66f7dcce5f --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/GetItemApiMethodParser.java @@ -0,0 +1,21 @@ +package org.evomaster.client.java.controller.dynamodb.parsers; + +import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; + +import java.util.Map; + +public class GetItemApiMethodParser extends DynamoDbBaseApiMethodParser { + + @Override + public DynamoDbOperationNames apiMethodName() { + return DynamoDbOperationNames.GET_ITEM; + } + + @Override + public Map parseRequest(Object request) { + String tableName = readValidTableName(request); + QueryOperation keyCondition = parseKeyCondition(request); + return singleTableResult(tableName, keyCondition); + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/PutItemApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/PutItemApiMethodParser.java new file mode 100644 index 0000000000..6f5889520b --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/PutItemApiMethodParser.java @@ -0,0 +1,16 @@ +package org.evomaster.client.java.controller.dynamodb.parsers; + +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; + +public class PutItemApiMethodParser extends WriteMethodParser { + + @Override + public DynamoDbOperationNames apiMethodName() { + return DynamoDbOperationNames.PUT_ITEM; + } + + @Override + protected boolean requiresKeyCondition() { + return false; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/QueryApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/QueryApiMethodParser.java new file mode 100644 index 0000000000..83d113927f --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/QueryApiMethodParser.java @@ -0,0 +1,39 @@ +package org.evomaster.client.java.controller.dynamodb.parsers; + +import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; + +import java.util.Collections; +import java.util.Map; + +public class QueryApiMethodParser extends DynamoDbBaseApiMethodParser { + + @Override + public DynamoDbOperationNames apiMethodName() { + return DynamoDbOperationNames.QUERY; + } + + @Override + public Map parseRequest(Object request) { + String tableName = readValidTableName(request); + if (tableName == null) { + return Collections.emptyMap(); + } + + Map names = readNameMap(request); + Map values = readValueMap(request); + + QueryOperation keyCondition = parseExpression( + readString(request, METHOD_KEY_CONDITION_EXPRESSION), + names, + values + ); + QueryOperation filterCondition = parseExpression( + readString(request, METHOD_FILTER_EXPRESSION), + names, + values + ); + + return singleTableResult(tableName, combineWithAnd(keyCondition, filterCondition)); + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/ScanApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/ScanApiMethodParser.java new file mode 100644 index 0000000000..af760f1159 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/ScanApiMethodParser.java @@ -0,0 +1,31 @@ +package org.evomaster.client.java.controller.dynamodb.parsers; + +import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; + +import java.util.Collections; +import java.util.Map; + +public class ScanApiMethodParser extends DynamoDbBaseApiMethodParser { + + @Override + public DynamoDbOperationNames apiMethodName() { + return DynamoDbOperationNames.SCAN; + } + + @Override + public Map parseRequest(Object request) { + String tableName = readValidTableName(request); + if (tableName == null) { + return Collections.emptyMap(); + } + + QueryOperation filterCondition = parseExpression( + readString(request, METHOD_FILTER_EXPRESSION), + readNameMap(request), + readValueMap(request) + ); + + return singleTableResult(tableName, filterCondition); + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/UpdateItemApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/UpdateItemApiMethodParser.java new file mode 100644 index 0000000000..e212909048 --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/UpdateItemApiMethodParser.java @@ -0,0 +1,16 @@ +package org.evomaster.client.java.controller.dynamodb.parsers; + +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; + +public class UpdateItemApiMethodParser extends WriteMethodParser { + + @Override + public DynamoDbOperationNames apiMethodName() { + return DynamoDbOperationNames.UPDATE_ITEM; + } + + @Override + protected boolean requiresKeyCondition() { + return true; + } +} diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/WriteMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/WriteMethodParser.java new file mode 100644 index 0000000000..f4be7e131a --- /dev/null +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/WriteMethodParser.java @@ -0,0 +1,28 @@ +package org.evomaster.client.java.controller.dynamodb.parsers; + +import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; + +import java.util.Collections; +import java.util.Map; + +abstract class WriteMethodParser extends DynamoDbBaseApiMethodParser { + + protected abstract boolean requiresKeyCondition(); + + @Override + public final Map parseRequest(Object request) { + String tableName = readValidTableName(request); + if (tableName == null) { + return Collections.emptyMap(); + } + + QueryOperation keyCondition = requiresKeyCondition() ? parseKeyCondition(request) : null; + QueryOperation conditionExpression = parseExpression( + readString(request, METHOD_CONDITION_EXPRESSION), + readNameMap(request), + readValueMap(request) + ); + + return singleTableResult(tableName, combineWithAnd(keyCondition, conditionExpression)); + } +} diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelperTest.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelperTest.java new file mode 100644 index 0000000000..c0aa64b443 --- /dev/null +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelperTest.java @@ -0,0 +1,183 @@ +package org.evomaster.client.java.controller.dynamodb; + +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.SdkBytes; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.nio.ByteBuffer; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class DynamoDbAttributeValueHelperTest { + + @Test + public void testToPlainMapWithNonMapReturnsEmpty() { + assertTrue(DynamoDbAttributeValueHelper.toPlainMap("world-cup").isEmpty()); + } + + @Test + public void testToPlainMapConvertsKeysAndSkipsNullKeys() { + Map source = new LinkedHashMap<>(); + source.put(10, AttributeValue.builder().s("Lionel Messi").build()); + source.put(null, AttributeValue.builder().s("Kylian Mbappe").build()); + + Map plain = DynamoDbAttributeValueHelper.toPlainMap(source); + + assertEquals(1, plain.size()); + assertEquals("Lionel Messi", plain.get("10")); + } + + @Test + public void testToPlainValueForNullMapAndCollection() { + assertNull(DynamoDbAttributeValueHelper.toPlainValue(null)); + + Map map = new LinkedHashMap<>(); + map.put("goals", AttributeValue.builder().n("7").build()); + assertEquals(7L, ((Map) DynamoDbAttributeValueHelper.toPlainValue(map)).get("goals")); + + List list = Arrays.asList( + AttributeValue.builder().s("Argentina").build(), + AttributeValue.builder().bool(true).build() + ); + assertEquals(Arrays.asList("Argentina", true), DynamoDbAttributeValueHelper.toPlainValue(list)); + } + + @Test + public void testToPlainValueWithNulHasPriority() { + AttributeValue value = AttributeValue.builder().nul(true).s("Messi").n("36").bool(true).build(); + assertNull(DynamoDbAttributeValueHelper.toPlainValue(value)); + } + + @Test + public void testToPlainValueWithNumberParsingVariants() { + assertEquals(13L, DynamoDbAttributeValueHelper.toPlainValue(AttributeValue.builder().n("13").build())); + assertEquals(1.75, (Double) DynamoDbAttributeValueHelper.toPlainValue(AttributeValue.builder().n("1.75").build()), 0.000001); + assertEquals(30.0, (Double) DynamoDbAttributeValueHelper.toPlainValue(AttributeValue.builder().n("3e1").build()), 0.000001); + + Object invalid = DynamoDbAttributeValueHelper.toPlainValue(AttributeValue.builder().n("goals").build()); + assertInstanceOf(Double.class, invalid); + assertTrue(Double.isNaN((Double) invalid)); + } + + @Test + public void testToPlainValueWithEmptyNumberFallsBackToBool() { + Object value = DynamoDbAttributeValueHelper.toPlainValue(new FakeAttributeValue("", true)); + assertEquals(true, value); + } + + @Test + public void testToPlainValueWithMapListAndSetShapes() { + Map nested = new LinkedHashMap<>(); + nested.put("player", AttributeValue.builder().s("Mbappe").build()); + + assertEquals(Collections.singletonMap("player", "Mbappe"), + DynamoDbAttributeValueHelper.toPlainValue(AttributeValue.builder().m(nested).build())); + + List list = Arrays.asList( + AttributeValue.builder().s("France").build(), + AttributeValue.builder().n("8").build() + ); + assertEquals(Arrays.asList("France", 8L), + DynamoDbAttributeValueHelper.toPlainValue(AttributeValue.builder().l(list).build())); + + Object ss = DynamoDbAttributeValueHelper.toPlainValue(AttributeValue.builder().ss("Argentina", "Argentina", "France").build()); + assertEquals(new LinkedHashSet<>(Arrays.asList("Argentina", "France")), ss); + + Object ns = DynamoDbAttributeValueHelper.toPlainValue(AttributeValue.builder().ns("36", "7.5", "age?").build()); + assertInstanceOf(Set.class, ns); + assertEquals(3, ((Set) ns).size()); + assertTrue(((Set) ns).contains(36L)); + assertTrue(((Set) ns).contains(7.5)); + assertTrue(((Set) ns).stream().anyMatch(v -> v instanceof Double && Double.isNaN((Double) v))); + } + + @Test + public void testToPlainValueWithBinaryAndBinarySet() { + SdkBytes binary = SdkBytes.fromByteArray(new byte[]{1, 2, 3}); + Object single = DynamoDbAttributeValueHelper.toPlainValue(AttributeValue.builder().b(binary).build()); + assertEquals(binary, single); + + SdkBytes bsBinary = SdkBytes.fromByteArray(new byte[]{7, 8}); + Object bs = DynamoDbAttributeValueHelper.toPlainValue(AttributeValue.builder().bs(bsBinary).build()); + assertInstanceOf(Set.class, bs); + assertEquals(1, ((Set) bs).size()); + assertEquals(bsBinary, ((Set) bs).iterator().next()); + } + + @Test + public void testToPlainValueWithDirectByteBufferBinary() { + Object converted = DynamoDbAttributeValueHelper.toPlainValue(new FakeBinaryAttributeValue(ByteBuffer.wrap(new byte[]{4, 5}))); + assertArrayEquals(new byte[]{4, 5}, (byte[]) converted); + } + + @Test + public void testToPlainValueWithBinarySetContainingNonBinary() { + Object converted = DynamoDbAttributeValueHelper.toPlainValue( + new FakeBinarySetAttributeValue(Arrays.asList(ByteBuffer.wrap(new byte[]{9}), "Brazil")) + ); + + assertInstanceOf(Set.class, converted); + assertEquals(2, ((Set) converted).size()); + Iterator it = ((Set) converted).iterator(); + assertArrayEquals(new byte[]{9}, (byte[]) it.next()); + assertEquals("Brazil", it.next()); + } + + @Test + public void testToPlainValueFallbackWhenNoKnownShape() { + Object marker = new Object(); + assertSame(marker, DynamoDbAttributeValueHelper.toPlainValue(marker)); + } + + private static class FakeAttributeValue { + private final String n; + private final Boolean bool; + + private FakeAttributeValue(String n, Boolean bool) { + this.n = n; + this.bool = bool; + } + + @SuppressWarnings("unused") + public String n() { + return n; + } + + @SuppressWarnings("unused") + public Boolean bool() { + return bool; + } + } + + private static class FakeBinarySetAttributeValue { + private final Collection bs; + + private FakeBinarySetAttributeValue(Collection bs) { + this.bs = bs; + } + + @SuppressWarnings("unused") + public Boolean hasBs() { + return true; + } + + @SuppressWarnings("unused") + public Collection bs() { + return bs; + } + } + + private static class FakeBinaryAttributeValue { + private final Object b; + + private FakeBinaryAttributeValue(Object b) { + this.b = b; + } + + @SuppressWarnings("unused") + public Object b() { + return b; + } + } +} diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParserTest.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParserTest.java new file mode 100644 index 0000000000..f9149e7fc1 --- /dev/null +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParserTest.java @@ -0,0 +1,123 @@ +package org.evomaster.client.java.controller.dynamodb; + +import org.evomaster.client.java.controller.dynamodb.operations.*; +import org.evomaster.client.java.controller.dynamodb.operations.comparison.*; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.*; + +public class DynamoDbExpressionParserTest extends DynamoDbTestBase { + + private final DynamoDbExpressionParser parser = new DynamoDbExpressionParser(); + + @Test + public void testBlankAndInvalidExpressions() { + assertNull(parser.parse(null, Collections.emptyMap(), Collections.emptyMap())); + assertNull(parser.parse(" ", Collections.emptyMap(), Collections.emptyMap())); + assertThrows(IllegalArgumentException.class, + () -> parser.parse("id =", Collections.emptyMap(), Collections.emptyMap())); + } + + @Test + public void testComparisonOperatorsAndValueKinds() { + assertComparison(parser.parse("playerName = 'Messi'", null, null), + EqualsOperation.class, "playerName", "Messi"); + + assertComparison(parser.parse("worldCups <> 3", null, null), + NotEqualsOperation.class, "worldCups", 3L); + + ComparisonOperation gt = castAs(parser.parse("internationalCaps > 1.5e2", null, null), GreaterThanOperation.class); + assertEquals("internationalCaps", gt.getFieldName()); + assertInstanceOf(Double.class, gt.getValue()); + assertEquals(150.0, (Double) gt.getValue(), 0.000001); + + assertComparison( + parser.parse("age >= :v", null, values(":v", 38L)), + GreaterThanEqualsOperation.class, "age", 38L); + + assertComparison(parser.parse("retired < FALSE", null, null), + LessThanOperation.class, "retired", false); + + assertComparison(parser.parse("nickname <= NULL", null, null), + LessThanEqualsOperation.class, "nickname", null); + } + + @Test + public void testFunctionsLogicalCompositionAliasesAndIndexes() { + QueryOperation operation = parser.parse( + "NOT (attribute_exists(#a[0].#b) AND begins_with(email, :p) AND contains(titles, 'World Cup')) " + + "OR attribute_type(legendType, S) " + + "OR size(teams) >= 2", + names("#a", "squads", "#b", "captain"), + values(":p", "messi@") + ); + + OrOperation rootOr = castAs(operation, OrOperation.class); + assertEquals(3, rootOr.getConditions().size()); + + NotOperation not = castAs(rootOr.getConditions().get(0), NotOperation.class); + AndOperation and = castAs(not.getCondition(), AndOperation.class); + assertEquals(3, and.getConditions().size()); + + ExistsOperation exists = castAs(and.getConditions().get(0), ExistsOperation.class); + assertEquals("squads[0].captain", exists.getFieldName()); + assertTrue(exists.isExists()); + + BeginsWithOperation beginsWith = castAs(and.getConditions().get(1), BeginsWithOperation.class); + assertEquals("email", beginsWith.getFieldName()); + assertEquals("messi@", beginsWith.getPrefix()); + + ContainsOperation contains = castAs(and.getConditions().get(2), ContainsOperation.class); + assertEquals("titles", contains.getFieldName()); + assertEquals("World Cup", contains.getExpectedValue()); + + TypeOperation type = castAs(rootOr.getConditions().get(1), TypeOperation.class); + assertEquals("legendType", type.getFieldName()); + assertEquals("S", type.getExpectedType()); + + SizeOperation size = castAs(rootOr.getConditions().get(2), SizeOperation.class); + assertEquals("teams", size.getFieldName()); + assertEquals(DynamoDbComparisonType.GREATER_THAN_EQUALS, size.getComparator()); + assertEquals(2L, size.getExpectedValue()); + } + + @Test + public void testBetweenAndInWithMixedValues() { + QueryOperation operation = parser.parse( + "age BETWEEN :low AND 41 AND playerName IN (:s1, 'Maradona', Pele)", + null, + values(":low", 38L, ":s1", "Messi") + ); + + AndOperation and = castAs(operation, AndOperation.class); + assertEquals(2, and.getConditions().size()); + + BetweenOperation between = castAs(and.getConditions().get(0), BetweenOperation.class); + assertEquals("age", between.getFieldName()); + assertEquals(38L, between.getLowerBound()); + assertEquals(41L, between.getUpperBound()); + + InOperation in = castAs(and.getConditions().get(1), InOperation.class); + assertEquals("playerName", in.getFieldName()); + assertEquals(Arrays.asList("Messi", "Maradona", "Pele"), in.getValues()); + } + + @Test + public void testFallbacksForMissingPlaceholderAliasAndOverflowNumberLiteral() { + assertComparison( + parser.parse("#playerId = :missing", Collections.emptyMap(), Collections.emptyMap()), + EqualsOperation.class, + "#playerId", + null + ); + + ComparisonOperation comparison = castAs(parser.parse("allTimeGoals = 9999999999999999999999999999999999999", + null, null), EqualsOperation.class); + assertEquals("allTimeGoals", comparison.getFieldName()); + //Assert the number was parsed as a String as a fallback + assertEquals("9999999999999999999999999999999999999", comparison.getValue()); + } +} diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParserTest.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParserTest.java new file mode 100644 index 0000000000..5ab993c983 --- /dev/null +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParserTest.java @@ -0,0 +1,361 @@ +package org.evomaster.client.java.controller.dynamodb; + +import org.evomaster.client.java.controller.dynamodb.operations.*; +import org.evomaster.client.java.controller.dynamodb.operations.comparison.*; +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import software.amazon.awssdk.services.dynamodb.model.*; + +import static org.junit.jupiter.api.Assertions.*; + + +public class DynamoDbRequestParserTest extends DynamoDbTestBase { + + private final DynamoDbRequestParser parser = new DynamoDbRequestParser(); + + @Test + public void testParseByTableGuards() { + assertTrue(parser.parseByTable(null, DynamoDbOperationNames.QUERY).isEmpty()); + assertTrue(parser.parseByTable(QueryRequest.builder().tableName("players").build(), null).isEmpty()); + + QueryRequest request = QueryRequest.builder() + .tableName("players") + .keyConditionExpression("id = :id") + .expressionAttributeValues(attributeValues(":id", stringValue("messi-10"))) + .build(); + + QueryOperation operation = parser.parseByTable(request, DynamoDbOperationNames.QUERY).get("players"); + assertComparison(operation, EqualsOperation.class, "id", "messi-10"); + } + + @Test + public void testQueryParsesKeyAndFilterExpressions() { + QueryRequest request = QueryRequest.builder() + .tableName("players") + .keyConditionExpression("#pk = :id") + .filterExpression("(age >= :min AND begins_with(#email, :prefix)) OR attribute_not_exists(#deleted)") + .expressionAttributeNames(names( + "#pk", "id", + "#email", "email", + "#deleted", "deletedAt" + )) + .expressionAttributeValues(attributeValues( + ":id", stringValue("messi-10"), + ":min", numberValue("38"), + ":prefix", stringValue("messi@") + )) + .build(); + + QueryOperation operation = parser.parseByTable(request, DynamoDbOperationNames.QUERY).get("players"); + AndOperation topAnd = castAs(operation, AndOperation.class); + assertEquals(2, topAnd.getConditions().size()); + + assertComparison(topAnd.getConditions().get(0), EqualsOperation.class, "id", "messi-10"); + + OrOperation filterOr = castAs(topAnd.getConditions().get(1), OrOperation.class); + assertEquals(2, filterOr.getConditions().size()); + + AndOperation nestedAnd = castAs(filterOr.getConditions().get(0), AndOperation.class); + assertEquals(2, nestedAnd.getConditions().size()); + assertComparison(nestedAnd.getConditions().get(0), GreaterThanEqualsOperation.class, "age", 38L); + + BeginsWithOperation beginsWith = castAs(nestedAnd.getConditions().get(1), BeginsWithOperation.class); + assertEquals("email", beginsWith.getFieldName()); + assertEquals("messi@", beginsWith.getPrefix()); + + ExistsOperation notExists = castAs(filterOr.getConditions().get(1), ExistsOperation.class); + assertEquals("deletedAt", notExists.getFieldName()); + assertFalse(notExists.isExists()); + } + + @Test + public void testQueryParsesLiteralValues() { + QueryRequest request = QueryRequest.builder() + .tableName("players") + .keyConditionExpression("id = 'ronaldo-7'") + .filterExpression("caps > 1.5e2 AND active = TRUE AND note = NULL") + .build(); + + QueryOperation operation = parser.parseByTable(request, DynamoDbOperationNames.QUERY).get("players"); + AndOperation topAnd = castAs(operation, AndOperation.class); + assertEquals(2, topAnd.getConditions().size()); + assertComparison(topAnd.getConditions().get(0), EqualsOperation.class, "id", "ronaldo-7"); + + AndOperation filterAnd = castAs(topAnd.getConditions().get(1), AndOperation.class); + assertEquals(3, filterAnd.getConditions().size()); + + ComparisonOperation greater = castAs(filterAnd.getConditions().get(0), GreaterThanOperation.class); + assertEquals("caps", greater.getFieldName()); + assertInstanceOf(Double.class, greater.getValue()); + assertEquals(150.0, (Double) greater.getValue(), 0.000001); + + assertComparison(filterAnd.getConditions().get(1), EqualsOperation.class, "active", true); + assertComparison(filterAnd.getConditions().get(2), EqualsOperation.class, "note", null); + } + + @Test + public void testScanParsesFilterOnly() { + ScanRequest request = ScanRequest.builder() + .tableName("players") + .filterExpression("contains(tags, :tag) AND size(tags) >= :n") + .expressionAttributeValues(attributeValues( + ":tag", stringValue("world-cup"), + ":n", numberValue("2") + )) + .build(); + + QueryOperation operation = parser.parseByTable(request, DynamoDbOperationNames.SCAN).get("players"); + AndOperation and = castAs(operation, AndOperation.class); + assertEquals(2, and.getConditions().size()); + + ContainsOperation contains = castAs(and.getConditions().get(0), ContainsOperation.class); + assertEquals("tags", contains.getFieldName()); + assertEquals("world-cup", contains.getExpectedValue()); + + SizeOperation size = castAs(and.getConditions().get(1), SizeOperation.class); + assertEquals("tags", size.getFieldName()); + assertEquals(2L, size.getExpectedValue()); + } + + @Test + public void testScanWithoutFilterReturnsEmptyMap() { + ScanRequest request = ScanRequest.builder() + .tableName("players") + .build(); + + assertTrue(parser.parseByTable(request, DynamoDbOperationNames.SCAN).isEmpty()); + } + + @Test + public void testScanWithBlankTableReturnsEmptyMap() { + ScanRequest request = ScanRequest.builder() + .tableName(" ") + .filterExpression("id = :id") + .expressionAttributeValues(attributeValues(":id", stringValue("messi-10"))) + .build(); + + assertTrue(parser.parseByTable(request, DynamoDbOperationNames.SCAN).isEmpty()); + } + + @Test + public void testPutItemParsesConditionOnly() { + PutItemRequest request = PutItemRequest.builder() + .tableName("players") + .conditionExpression("attribute_exists(#status) AND #status <> :old") + .expressionAttributeNames(names("#status", "status")) + .expressionAttributeValues(attributeValues(":old", stringValue("RETIRED"))) + .build(); + + QueryOperation operation = parser.parseByTable(request, DynamoDbOperationNames.PUT_ITEM).get("players"); + AndOperation and = castAs(operation, AndOperation.class); + assertEquals(2, and.getConditions().size()); + + ExistsOperation exists = castAs(and.getConditions().get(0), ExistsOperation.class); + assertEquals("status", exists.getFieldName()); + assertTrue(exists.isExists()); + + assertComparison(and.getConditions().get(1), NotEqualsOperation.class, "status", "RETIRED"); + } + + @Test + public void testPutItemWithBlankTableReturnsEmptyMap() { + PutItemRequest request = PutItemRequest.builder() + .tableName(" ") + .conditionExpression("id = :id") + .expressionAttributeValues(attributeValues(":id", stringValue("messi-10"))) + .build(); + + assertTrue(parser.parseByTable(request, DynamoDbOperationNames.PUT_ITEM).isEmpty()); + } + + @Test + public void testDeleteItemCombinesKeyAndCondition() { + DeleteItemRequest request = DeleteItemRequest.builder() + .tableName("players") + .key(attributeValues( + "id", stringValue("maradona-10"), + "tenant", stringValue("Argentina") + )) + .conditionExpression("version = :v") + .expressionAttributeValues(attributeValues(":v", numberValue("7"))) + .build(); + + QueryOperation operation = parser.parseByTable(request, DynamoDbOperationNames.DELETE_ITEM).get("players"); + AndOperation topAnd = castAs(operation, AndOperation.class); + assertEquals(2, topAnd.getConditions().size()); + + AndOperation keyAnd = castAs(topAnd.getConditions().get(0), AndOperation.class); + assertEquals(2, keyAnd.getConditions().size()); + assertComparison(keyAnd.getConditions().get(0), EqualsOperation.class, "id", "maradona-10"); + assertComparison(keyAnd.getConditions().get(1), EqualsOperation.class, "tenant", "Argentina"); + + assertComparison(topAnd.getConditions().get(1), EqualsOperation.class, "version", 7L); + } + + @Test + public void testUpdateItemCombinesKeyAndCondition() { + UpdateItemRequest request = UpdateItemRequest.builder() + .tableName("players") + .key(attributeValues("id", stringValue("ronaldo-7"))) + .conditionExpression("#age BETWEEN :l AND :u") + .expressionAttributeNames(names("#age", "age")) + .expressionAttributeValues(attributeValues( + ":l", numberValue("38"), + ":u", numberValue("41") + )) + .build(); + + QueryOperation operation = parser.parseByTable(request, DynamoDbOperationNames.UPDATE_ITEM).get("players"); + AndOperation and = castAs(operation, AndOperation.class); + assertEquals(2, and.getConditions().size()); + + assertComparison(and.getConditions().get(0), EqualsOperation.class, "id", "ronaldo-7"); + BetweenOperation between = castAs(and.getConditions().get(1), BetweenOperation.class); + assertEquals("age", between.getFieldName()); + assertEquals(38L, between.getLowerBound()); + assertEquals(41L, between.getUpperBound()); + } + + @Test + public void testGetItemParsesCompositeKey() { + GetItemRequest request = GetItemRequest.builder() + .tableName("players") + .key(attributeValues( + "id", stringValue("pele-10"), + "tenant", stringValue("brazil") + )) + .build(); + + QueryOperation operation = parser.parseByTable(request, DynamoDbOperationNames.GET_ITEM).get("players"); + AndOperation and = castAs(operation, AndOperation.class); + assertEquals(2, and.getConditions().size()); + assertComparison(and.getConditions().get(0), EqualsOperation.class, "id", "pele-10"); + assertComparison(and.getConditions().get(1), EqualsOperation.class, "tenant", "brazil"); + } + + @Test + public void testGetItemWithoutKeyReturnsEmptyMap() { + GetItemRequest request = GetItemRequest.builder() + .tableName("players") + .build(); + + assertTrue(parser.parseByTable(request, DynamoDbOperationNames.GET_ITEM).isEmpty()); + } + + @Test + public void testBatchGetParsesEachTableAndSkipsInvalidTableNames() { + Map requestItems = new LinkedHashMap<>(); + requestItems.put("players", KeysAndAttributes.builder().keys(Arrays.asList( + attributeValues("id", stringValue("messi-10")), + attributeValues("id", stringValue("ronaldo-7")) + )).build()); + requestItems.put("matches", KeysAndAttributes.builder().keys(Collections.singletonList( + attributeValues( + "matchId", stringValue("wc-final-1986"), + "tenant", stringValue("Mexico") + ) + )).build()); + requestItems.put("", KeysAndAttributes.builder().keys(Collections.singletonList( + attributeValues("id", stringValue("pele-10")) + )).build()); + + BatchGetItemRequest request = BatchGetItemRequest.builder() + .requestItems(requestItems) + .build(); + + Map parsed = parser.parseByTable(request, DynamoDbOperationNames.BATCH_GET_ITEM); + assertEquals(2, parsed.size()); + + OrOperation players = castAs(parsed.get("players"), OrOperation.class); + assertEquals(2, players.getConditions().size()); + assertComparison(players.getConditions().get(0), EqualsOperation.class, "id", "messi-10"); + assertComparison(players.getConditions().get(1), EqualsOperation.class, "id", "ronaldo-7"); + + AndOperation matches = castAs(parsed.get("matches"), AndOperation.class); + assertEquals(2, matches.getConditions().size()); + assertComparison(matches.getConditions().get(0), EqualsOperation.class, "matchId", "wc-final-1986"); + assertComparison(matches.getConditions().get(1), EqualsOperation.class, "tenant", "Mexico"); + } + + @Test + public void testBatchGetWithEmptyKeysDoesNotAddTable() { + BatchGetItemRequest request = BatchGetItemRequest.builder() + .requestItems(Collections.singletonMap( + "players", + KeysAndAttributes.builder().keys(Collections.singletonList( + Collections.emptyMap() + )).build() + )) + .build(); + + assertTrue(parser.parseByTable(request, DynamoDbOperationNames.BATCH_GET_ITEM).isEmpty()); + } + + @Test + public void testBatchGetSkipsInvalidRequestItemsShapes() { + assertTrue(parser.parseByTable(new RequestWithNonMapRequestItems(), DynamoDbOperationNames.BATCH_GET_ITEM).isEmpty()); + assertTrue(parser.parseByTable(new RequestWithNonCollectionKeys(), DynamoDbOperationNames.BATCH_GET_ITEM).isEmpty()); + } + + @Test + public void testConditionWithTypeAndInParsing() { + PutItemRequest request = PutItemRequest.builder() + .tableName("players") + .conditionExpression("attribute_type(kind, S) AND status IN (:s1, 'GOAT', champion)") + .expressionAttributeValues(attributeValues(":s1", stringValue("LEGEND"))) + .build(); + + QueryOperation operation = parser.parseByTable(request, DynamoDbOperationNames.PUT_ITEM).get("players"); + AndOperation and = castAs(operation, AndOperation.class); + assertEquals(2, and.getConditions().size()); + + TypeOperation type = castAs(and.getConditions().get(0), TypeOperation.class); + assertEquals("kind", type.getFieldName()); + assertEquals("S", type.getExpectedType()); + + InOperation in = castAs(and.getConditions().get(1), InOperation.class); + assertEquals("status", in.getFieldName()); + assertEquals(Arrays.asList("LEGEND", "GOAT", "champion"), in.getValues()); + } + + @Test + public void testBlankTableNameReturnsEmptyMap() { + QueryRequest request = QueryRequest.builder() + .tableName(" ") + .keyConditionExpression("id = :id") + .expressionAttributeValues(attributeValues(":id", stringValue("messi-10"))) + .build(); + + assertTrue(parser.parseByTable(request, DynamoDbOperationNames.QUERY).isEmpty()); + } + + private static class RequestWithNonMapRequestItems { + @SuppressWarnings("unused") // will be invoked by reflection + public Object requestItems() { + return "not-a-map"; + } + } + + //Reflection will invoke fake class, method + private static class RequestWithNonCollectionKeys { + @SuppressWarnings("unused") // will be invoked by reflection + public Map requestItems() { + Map map = new LinkedHashMap<>(); + map.put("players", new NonCollectionKeysHolder()); + return map; + } + } + + //Reflection will invoke fake class, method + private static class NonCollectionKeysHolder { + @SuppressWarnings("unused") // will be invoked by reflection + public Object keys() { + return "not-a-collection"; + } + } +} diff --git a/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbTestBase.java b/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbTestBase.java new file mode 100644 index 0000000000..41c5e7ef9b --- /dev/null +++ b/client-java/controller/src/test/java/org/evomaster/client/java/controller/dynamodb/DynamoDbTestBase.java @@ -0,0 +1,65 @@ +package org.evomaster.client.java.controller.dynamodb; + +import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; +import org.evomaster.client.java.controller.dynamodb.operations.comparison.ComparisonOperation; +import software.amazon.awssdk.services.dynamodb.model.AttributeValue; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import static org.junit.jupiter.api.Assertions.*; + +public abstract class DynamoDbTestBase { + + protected final Map values(Object... kv) { + return toMap(kv, value -> value); + } + + protected final Map names(Object... kv) { + return toMap(kv, String::valueOf); + } + + protected final Map attributeValues(Object... kv) { + return toMap(kv, value -> (AttributeValue) value); + } + + protected final AttributeValue stringValue(String value) { + return AttributeValue.builder().s(value).build(); + } + + protected final AttributeValue numberValue(String value) { + return AttributeValue.builder().n(value).build(); + } + + @SuppressWarnings({"rawtypes"}) + protected final void assertComparison( + QueryOperation operation, + Class expectedType, + String expectedField, + Object expectedValue) { + assertNotNull(operation); + assertTrue(expectedType.isInstance(operation)); + ComparisonOperation comparison = (ComparisonOperation) operation; + assertEquals(expectedField, comparison.getFieldName()); + assertEquals(expectedValue, comparison.getValue()); + } + + protected final T castAs(QueryOperation operation, Class type) { + assertNotNull(operation); + assertTrue(type.isInstance(operation)); + return type.cast(operation); + } + + private static Map toMap(Object[] kv, Function valueMapper) { + if (kv.length % 2 != 0) { + throw new IllegalArgumentException("Expected an even number of key/value arguments"); + } + + Map map = new LinkedHashMap<>(); + for (int i = 0; i < kv.length; i += 2) { + map.put(String.valueOf(kv[i]), valueMapper.apply(kv[i + 1])); + } + return map; + } +} diff --git a/client-java/controller/src/test/resources/simplelogger.properties b/client-java/controller/src/test/resources/simplelogger.properties new file mode 100644 index 0000000000..cd90c2acb8 --- /dev/null +++ b/client-java/controller/src/test/resources/simplelogger.properties @@ -0,0 +1 @@ +org.slf4j.simpleLogger.defaultLogLevel=warn diff --git a/client-java/instrumentation/pom.xml b/client-java/instrumentation/pom.xml index 141b78798b..2a530fef8a 100644 --- a/client-java/instrumentation/pom.xml +++ b/client-java/instrumentation/pom.xml @@ -143,9 +143,8 @@ test - org.hibernate + org.hibernate.validator hibernate-validator - 6.2.0.Final test diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/DynamoDbCommand.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/DynamoDbCommand.java index 12df40ca87..c7cf2cb9cd 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/DynamoDbCommand.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/DynamoDbCommand.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Info related to DynamoDB command execution. @@ -18,7 +19,7 @@ public class DynamoDbCommand implements Serializable { /** * Name of the operation that was executed */ - private final String operationName; + private final DynamoDbOperationNames operationName; /** * Actual executed operation */ @@ -32,11 +33,11 @@ public class DynamoDbCommand implements Serializable { */ private final long executionTime; - public DynamoDbCommand(List tableNames, String operationName, Object request, boolean successfullyExecuted, long executionTime) { + public DynamoDbCommand(List tableNames, DynamoDbOperationNames operationName, Object request, boolean successfullyExecuted, long executionTime) { this.tableNames = tableNames == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(tableNames)); - this.operationName = operationName; + this.operationName = Objects.requireNonNull(operationName, "operationName cannot be null"); this.request = request; this.successfullyExecuted = successfullyExecuted; this.executionTime = executionTime; @@ -46,7 +47,7 @@ public List getTableNames() { return tableNames; } - public String getOperationName() { + public DynamoDbOperationNames getOperationName() { return operationName; } diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/DynamoDbOperationNames.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/DynamoDbOperationNames.java new file mode 100644 index 0000000000..00212eb22c --- /dev/null +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/DynamoDbOperationNames.java @@ -0,0 +1,30 @@ +package org.evomaster.client.java.instrumentation; + +/** + * Shared DynamoDB API operation names used across instrumentation and parsing logic. + */ +public enum DynamoDbOperationNames { + + GET_ITEM("GetItem"), + BATCH_GET_ITEM("BatchGetItem"), + PUT_ITEM("PutItem"), + UPDATE_ITEM("UpdateItem"), + DELETE_ITEM("DeleteItem"), + QUERY("Query"), + SCAN("Scan"); + + private final String value; + + DynamoDbOperationNames(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/DynamoDbClassReplacement.java b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/DynamoDbClassReplacement.java index c78c215786..16a723df19 100644 --- a/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/DynamoDbClassReplacement.java +++ b/client-java/instrumentation/src/main/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/DynamoDbClassReplacement.java @@ -1,6 +1,7 @@ package org.evomaster.client.java.instrumentation.coverage.methodreplacement.thirdpartyclasses; import org.evomaster.client.java.instrumentation.DynamoDbCommand; +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; import org.evomaster.client.java.instrumentation.coverage.methodreplacement.Replacement; import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyCast; import org.evomaster.client.java.instrumentation.coverage.methodreplacement.ThirdPartyMethodReplacementClass; @@ -26,14 +27,6 @@ */ public class DynamoDbClassReplacement { - //DynamoDB API method names do not change them. - public static final String METHOD_GET_ITEM = "GetItem"; - public static final String METHOD_BATCH_GET_ITEM = "BatchGetItem"; - public static final String METHOD_PUT_ITEM = "PutItem"; - public static final String METHOD_UPDATE_ITEM = "UpdateItem"; - public static final String METHOD_DELETE_ITEM = "DeleteItem"; - public static final String METHOD_QUERY = "Query"; - public static final String METHOD_SCAN = "Scan"; public static final String METHOD_TABLE_NAME = "tableName"; public static final String METHOD_REQUEST_ITEMS = "requestItems"; @@ -54,37 +47,37 @@ protected String getNameOfThirdPartyTargetClass() { @Replacement(type = ReplacementType.TRACKER, id = DDB_GET_ITEM, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "software.amazon.awssdk.services.dynamodb.model.GetItemResponse") public static Object getItem(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.GetItemRequest") Object request) { - return handle(client, DDB_GET_ITEM, request, METHOD_GET_ITEM); + return handle(client, DDB_GET_ITEM, request, DynamoDbOperationNames.GET_ITEM); } @Replacement(type = ReplacementType.TRACKER, id = DDB_BATCH_GET_ITEM, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "software.amazon.awssdk.services.dynamodb.model.BatchGetItemResponse") public static Object batchGetItem(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.BatchGetItemRequest") Object request) { - return handle(client, DDB_BATCH_GET_ITEM, request, METHOD_BATCH_GET_ITEM); + return handle(client, DDB_BATCH_GET_ITEM, request, DynamoDbOperationNames.BATCH_GET_ITEM); } @Replacement(type = ReplacementType.TRACKER, id = DDB_PUT_ITEM, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "software.amazon.awssdk.services.dynamodb.model.PutItemResponse") public static Object putItem(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.PutItemRequest") Object request) { - return handle(client, DDB_PUT_ITEM, request, METHOD_PUT_ITEM); + return handle(client, DDB_PUT_ITEM, request, DynamoDbOperationNames.PUT_ITEM); } @Replacement(type = ReplacementType.TRACKER, id = DDB_UPDATE_ITEM, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse") public static Object updateItem(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest") Object request) { - return handle(client, DDB_UPDATE_ITEM, request, METHOD_UPDATE_ITEM); + return handle(client, DDB_UPDATE_ITEM, request, DynamoDbOperationNames.UPDATE_ITEM); } @Replacement(type = ReplacementType.TRACKER, id = DDB_DELETE_ITEM, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "software.amazon.awssdk.services.dynamodb.model.DeleteItemResponse") public static Object deleteItem(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest") Object request) { - return handle(client, DDB_DELETE_ITEM, request, METHOD_DELETE_ITEM); + return handle(client, DDB_DELETE_ITEM, request, DynamoDbOperationNames.DELETE_ITEM); } @Replacement(type = ReplacementType.TRACKER, id = DDB_QUERY, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "software.amazon.awssdk.services.dynamodb.model.QueryResponse") public static Object query(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.QueryRequest") Object request) { - return handle(client, DDB_QUERY, request, METHOD_QUERY); + return handle(client, DDB_QUERY, request, DynamoDbOperationNames.QUERY); } @Replacement(type = ReplacementType.TRACKER, id = DDB_SCAN, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "software.amazon.awssdk.services.dynamodb.model.ScanResponse") public static Object scan(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.ScanRequest") Object request) { - return handle(client, DDB_SCAN, request, METHOD_SCAN); + return handle(client, DDB_SCAN, request, DynamoDbOperationNames.SCAN); } } @@ -105,44 +98,44 @@ protected String getNameOfThirdPartyTargetClass() { @Replacement(type = ReplacementType.TRACKER, id = DDB_ASYNC_GET_ITEM, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "java.util.concurrent.CompletableFuture") public static Object getItem(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.GetItemRequest") Object request) { - return handleAsync(client, DDB_ASYNC_GET_ITEM, request, METHOD_GET_ITEM); + return handleAsync(client, DDB_ASYNC_GET_ITEM, request, DynamoDbOperationNames.GET_ITEM); } @Replacement(type = ReplacementType.TRACKER, id = DDB_ASYNC_BATCH_GET_ITEM, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "java.util.concurrent.CompletableFuture") public static Object batchGetItem(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.BatchGetItemRequest") Object request) { - return handleAsync( client, DDB_ASYNC_BATCH_GET_ITEM, request, METHOD_BATCH_GET_ITEM); + return handleAsync(client, DDB_ASYNC_BATCH_GET_ITEM, request, DynamoDbOperationNames.BATCH_GET_ITEM); } @Replacement(type = ReplacementType.TRACKER, id = DDB_ASYNC_PUT_ITEM, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "java.util.concurrent.CompletableFuture") public static Object putItem(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.PutItemRequest") Object request) { - return handleAsync(client, DDB_ASYNC_PUT_ITEM, request, METHOD_PUT_ITEM); + return handleAsync(client, DDB_ASYNC_PUT_ITEM, request, DynamoDbOperationNames.PUT_ITEM); } @Replacement(type = ReplacementType.TRACKER, id = DDB_ASYNC_UPDATE_ITEM, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "java.util.concurrent.CompletableFuture") public static Object updateItem(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest") Object request) { - return handleAsync(client, DDB_ASYNC_UPDATE_ITEM, request, METHOD_UPDATE_ITEM); + return handleAsync(client, DDB_ASYNC_UPDATE_ITEM, request, DynamoDbOperationNames.UPDATE_ITEM); } @Replacement(type = ReplacementType.TRACKER, id = DDB_ASYNC_DELETE_ITEM, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "java.util.concurrent.CompletableFuture") public static Object deleteItem(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest") Object request) { - return handleAsync(client, DDB_ASYNC_DELETE_ITEM, request, METHOD_DELETE_ITEM); + return handleAsync(client, DDB_ASYNC_DELETE_ITEM, request, DynamoDbOperationNames.DELETE_ITEM); } @Replacement(type = ReplacementType.TRACKER, id = DDB_ASYNC_QUERY, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "java.util.concurrent.CompletableFuture") public static Object query(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.QueryRequest") Object request) { - return handleAsync(client, DDB_ASYNC_QUERY, request, METHOD_QUERY); + return handleAsync(client, DDB_ASYNC_QUERY, request, DynamoDbOperationNames.QUERY); } @Replacement(type = ReplacementType.TRACKER, id = DDB_ASYNC_SCAN, usageFilter = UsageFilter.ANY, category = ReplacementCategory.DYNAMODB, castTo = "java.util.concurrent.CompletableFuture") public static Object scan(Object client, @ThirdPartyCast(actualType = "software.amazon.awssdk.services.dynamodb.model.ScanRequest") Object request) { - return handleAsync(client, DDB_ASYNC_SCAN, request, METHOD_SCAN); + return handleAsync(client, DDB_ASYNC_SCAN, request, DynamoDbOperationNames.SCAN); } } /** * Invoke the original synchronous client method and trace the command execution. */ - protected static Object handle(Object client, String id, Object request, String operationName) { + protected static Object handle(Object client, String id, Object request, DynamoDbOperationNames operationName) { long start = System.currentTimeMillis(); try { Method method = getOriginal(Sync.singleton, id, client); @@ -165,7 +158,7 @@ protected static Object handle(Object client, String id, Object request, String /** * Invoke the original asynchronous client method and trace completion status. */ - protected static Object handleAsync(Object client, String id, Object request, String operationName) { + protected static Object handleAsync(Object client, String id, Object request, DynamoDbOperationNames operationName) { long start = System.currentTimeMillis(); try { Method method = getOriginal(Async.singleton, id, client); diff --git a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/DynamoDbClassReplacementTest.java b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/DynamoDbClassReplacementTest.java index fd3e26bf99..cf0a79beb9 100644 --- a/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/DynamoDbClassReplacementTest.java +++ b/client-java/instrumentation/src/test/java/org/evomaster/client/java/instrumentation/coverage/methodreplacement/thirdpartyclasses/DynamoDbClassReplacementTest.java @@ -2,6 +2,7 @@ import org.evomaster.client.java.instrumentation.AdditionalInfo; import org.evomaster.client.java.instrumentation.DynamoDbCommand; +import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; import org.evomaster.client.java.instrumentation.staticstate.ExecutionTracer; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -118,7 +119,7 @@ public void testGetItem() { GetItemResponse result = (GetItemResponse) DynamoDbClassReplacement.Sync.getItem(syncClient, request); assertNotNull(result); - verifyInterception(Collections.singletonList(TABLE_NAME), "GetItem", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.GET_ITEM, request); } @Test @@ -131,7 +132,7 @@ public void testPutItem() { PutItemResponse result = (PutItemResponse) DynamoDbClassReplacement.Sync.putItem(syncClient, request); assertNotNull(result); - verifyInterception(Collections.singletonList(TABLE_NAME), "PutItem", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.PUT_ITEM, request); } @Test @@ -147,7 +148,7 @@ public void testUpdateItem() { UpdateItemResponse result = (UpdateItemResponse) DynamoDbClassReplacement.Sync.updateItem(syncClient, request); assertNotNull(result); - verifyInterception(Collections.singletonList(TABLE_NAME), "UpdateItem", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.UPDATE_ITEM, request); } @Test @@ -160,7 +161,7 @@ public void testDeleteItem() { DeleteItemResponse result = (DeleteItemResponse) DynamoDbClassReplacement.Sync.deleteItem(syncClient, request); assertNotNull(result); - verifyInterception(Collections.singletonList(TABLE_NAME), "DeleteItem", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.DELETE_ITEM, request); } @Test @@ -174,7 +175,7 @@ public void testQuery() { QueryResponse result = (QueryResponse) DynamoDbClassReplacement.Sync.query(syncClient, request); assertNotNull(result); - verifyInterception(Collections.singletonList(TABLE_NAME), "Query", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.QUERY, request); } @Test @@ -186,7 +187,7 @@ public void testScan() { ScanResponse result = (ScanResponse) DynamoDbClassReplacement.Sync.scan(syncClient, request); assertNotNull(result); - verifyInterception(Collections.singletonList(TABLE_NAME), "Scan", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.SCAN, request); } @Test @@ -205,7 +206,7 @@ public void testBatchGetItem() { BatchGetItemResponse result = (BatchGetItemResponse) DynamoDbClassReplacement.Sync.batchGetItem(syncClient, request); assertNotNull(result); - verifyInterception(Arrays.asList(TABLE_NAME, TABLE_NAME_SECOND), "BatchGetItem", request); + verifyInterception(Arrays.asList(TABLE_NAME, TABLE_NAME_SECOND), DynamoDbOperationNames.BATCH_GET_ITEM, request); } // --- Async Tests --- @@ -225,7 +226,7 @@ public void testGetItemAsync() { } catch (Exception e) { // ignore } - verifyInterception(Collections.singletonList(TABLE_NAME), "GetItem", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.GET_ITEM, request); } @Test @@ -243,7 +244,7 @@ public void testPutItemAsync() { } catch (Exception e) { // ignore } - verifyInterception(Collections.singletonList(TABLE_NAME), "PutItem", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.PUT_ITEM, request); } @Test @@ -264,7 +265,7 @@ public void testUpdateItemAsync() { } catch (Exception e) { // ignore } - verifyInterception(Collections.singletonList(TABLE_NAME), "UpdateItem", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.UPDATE_ITEM, request); } @Test @@ -282,7 +283,7 @@ public void testDeleteItemAsync() { } catch (Exception e) { // ignore } - verifyInterception(Collections.singletonList(TABLE_NAME), "DeleteItem", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.DELETE_ITEM, request); } @Test @@ -301,7 +302,7 @@ public void testQueryAsync() { } catch (Exception e) { // ignore } - verifyInterception(Collections.singletonList(TABLE_NAME), "Query", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.QUERY, request); } @Test @@ -318,7 +319,7 @@ public void testScanAsync() { } catch (Exception e) { // ignore } - verifyInterception(Collections.singletonList(TABLE_NAME), "Scan", request); + verifyInterception(Collections.singletonList(TABLE_NAME), DynamoDbOperationNames.SCAN, request); } @Test @@ -341,10 +342,10 @@ public void testBatchGetItemAsync() { } catch (Exception e) { // ignore } - verifyInterception(Arrays.asList(TABLE_NAME, TABLE_NAME_SECOND), "BatchGetItem", request); + verifyInterception(Arrays.asList(TABLE_NAME, TABLE_NAME_SECOND), DynamoDbOperationNames.BATCH_GET_ITEM, request); } - private void verifyInterception(List expectedTableNames, String expectedOperationName, Object expectedRequest) { + private void verifyInterception(List expectedTableNames, DynamoDbOperationNames expectedOperationName, Object expectedRequest) { List additionalInfoList = ExecutionTracer.exposeAdditionalInfoList(); assertEquals(1, additionalInfoList.size()); Set dynamoDbCommands = additionalInfoList.get(0).getDynamoDbInfoData(); diff --git a/core-parent/pom.xml b/core-parent/pom.xml index d7926a4247..b41347ec3d 100644 --- a/core-parent/pom.xml +++ b/core-parent/pom.xml @@ -36,7 +36,7 @@ - org.hibernate + org.hibernate.validator hibernate-validator From 4477f1a0f369795aab8ceb8b8a74ca180e103f12 Mon Sep 17 00:00:00 2001 From: aschenzle Date: Thu, 23 Apr 2026 16:19:30 -0700 Subject: [PATCH 2/3] Adding javadoc --- .../DynamoDbAttributeValueHelper.java | 53 +++++++++++++ .../dynamodb/DynamoDbComparisonType.java | 13 ++++ .../dynamodb/DynamoDbExpressionParser.java | 6 ++ .../dynamodb/DynamoDbReflectionHelper.java | 17 +++++ .../dynamodb/DynamoDbRequestParser.java | 11 ++- .../dynamodb/operations/AndOperation.java | 13 ++++ .../operations/BeginsWithOperation.java | 9 +++ .../dynamodb/operations/BetweenOperation.java | 10 +++ .../operations/ContainsOperation.java | 9 +++ .../dynamodb/operations/ExistsOperation.java | 10 +++ .../dynamodb/operations/InOperation.java | 11 +++ .../dynamodb/operations/NotOperation.java | 8 ++ .../dynamodb/operations/OrOperation.java | 13 ++++ .../dynamodb/operations/SizeOperation.java | 10 +++ .../dynamodb/operations/TypeOperation.java | 9 +++ .../comparison/ComparisonOperation.java | 11 +++ .../comparison/EqualsOperation.java | 11 +++ .../GreaterThanEqualsOperation.java | 11 +++ .../comparison/GreaterThanOperation.java | 11 +++ .../comparison/LessThanEqualsOperation.java | 11 +++ .../comparison/LessThanOperation.java | 11 +++ .../comparison/NotEqualsOperation.java | 11 +++ .../parsers/BatchGetItemApiMethodParser.java | 15 ++++ .../parsers/DeleteItemApiMethodParser.java | 9 +++ .../parsers/DynamoDbApiMethodParser.java | 11 +++ .../parsers/DynamoDbBaseApiMethodParser.java | 75 +++++++++++++++++++ .../parsers/GetItemApiMethodParser.java | 9 +++ .../parsers/PutItemApiMethodParser.java | 9 +++ .../parsers/QueryApiMethodParser.java | 9 +++ .../dynamodb/parsers/ScanApiMethodParser.java | 9 +++ .../parsers/UpdateItemApiMethodParser.java | 9 +++ .../dynamodb/parsers/WriteMethodParser.java | 11 +++ 32 files changed, 444 insertions(+), 1 deletion(-) diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelper.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelper.java index d55b85444c..1ac2a78c64 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelper.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbAttributeValueHelper.java @@ -33,9 +33,18 @@ public final class DynamoDbAttributeValueHelper { private static final String SCIENTIFIC_NOTATION_E_LOWER = "e"; private static final String SCIENTIFIC_NOTATION_E_UPPER = "E"; + /** + * Utility class, no instances. + */ private DynamoDbAttributeValueHelper() { } + /** + * Converts a map of DynamoDB attribute values into plain Java values. + * + * @param source input object expected to be a map + * @return normalized map or empty map when input is not a map + */ public static Map toPlainMap(Object source) { if (!(source instanceof Map)) { return Collections.emptyMap(); @@ -50,6 +59,12 @@ public static Map toPlainMap(Object source) { return result; } + /** + * Converts one DynamoDB attribute value object into a plain Java value. + * + * @param value attribute value object + * @return normalized Java value + */ @SuppressWarnings("unchecked") public static Object toPlainValue(Object value) { if (value == null) { @@ -119,6 +134,12 @@ public static Object toPlainValue(Object value) { return value; } + /** + * Converts binary payloads into plain byte arrays when backed by ByteBuffer. + * + * @param value binary payload object + * @return byte array or original value when conversion is not needed + */ private static Object toPlainBinary(Object value) { if (value instanceof ByteBuffer) { ByteBuffer bb = ((ByteBuffer) value).asReadOnlyBuffer(); @@ -130,6 +151,14 @@ private static Object toPlainBinary(Object value) { return value; } + /** + * Reads a reflected value only when its corresponding {@code hasX} accessor is true. + * + * @param target target object + * @param hasMethod presence-check method name + * @param valueMethod value accessor method name + * @return reflected value or {@code null} + */ private static Object readIfPresent(Object target, String hasMethod, String valueMethod) { if (Boolean.TRUE.equals(DynamoDbReflectionHelper.invokeBooleanNoArg(target, hasMethod))) { return DynamoDbReflectionHelper.invokeNoArg(target, valueMethod); @@ -137,6 +166,12 @@ private static Object readIfPresent(Object target, String hasMethod, String valu return null; } + /** + * Converts a collection of attribute values into plain Java values. + * + * @param source source collection + * @return normalized list + */ private static List toPlainList(Collection source) { List converted = new ArrayList<>(source.size()); for (Object element : source) { @@ -145,6 +180,12 @@ private static List toPlainList(Collection source) { return converted; } + /** + * Converts a collection of numeric tokens into parsed numeric values. + * + * @param source source numeric collection + * @return normalized number set + */ private static Set toNumberSet(Collection source) { LinkedHashSet numbers = new LinkedHashSet<>(); for (Object number : source) { @@ -155,6 +196,12 @@ private static Set toNumberSet(Collection source) { return numbers; } + /** + * Converts a collection of binary payloads into plain binary values. + * + * @param source source binary collection + * @return normalized binary set + */ private static Set toBinarySet(Collection source) { LinkedHashSet binaries = new LinkedHashSet<>(); for (Object binary : source) { @@ -163,6 +210,12 @@ private static Set toBinarySet(Collection source) { return binaries; } + /** + * Parses a numeric token into {@link Long} or {@link Double}. + * + * @param text numeric token + * @return parsed number or {@link Double#NaN} when parsing fails + */ private static Object parseNumber(String text) { try { if (text.contains(DECIMAL_SEPARATOR) diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java index 9acc303af5..f9d4860a4f 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java @@ -13,6 +13,12 @@ public enum DynamoDbComparisonType { LESS_THAN, LESS_THAN_EQUALS; + /** + * Maps a DynamoDB comparator token to a normalized comparison type. + * + * @param token comparator token from parsed expression + * @return normalized comparison type + */ public static DynamoDbComparisonType fromToken(String token) { if ("=".equals(token)) { return EQUALS; @@ -35,6 +41,13 @@ public static DynamoDbComparisonType fromToken(String token) { throw new IllegalArgumentException("Unsupported comparator token: " + token); } + /** + * Creates a comparison operation instance for this comparison type. + * + * @param fieldName compared field name/path + * @param value comparison value + * @return concrete comparison operation + */ public ComparisonOperation toOperation(String fieldName, Object value) { switch (this) { case EQUALS: diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java index c1b67c4712..1b943aa374 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java @@ -171,6 +171,9 @@ private QueryOperation mergeOr(QueryOperation left, QueryOperation right) { return new OrOperation(Arrays.asList(left, right)); } + /** + * Visitor that converts grammar value nodes into Java values. + */ private class ValueVisitor extends DynamoDbConditionExpressionBaseVisitor { /** {@inheritDoc} */ @@ -211,6 +214,9 @@ public Object visitIdentifierValue(DynamoDbConditionExpressionParser.IdentifierV } } + /** + * Visitor that converts grammar predicate nodes into query operations. + */ private class OperationVisitor extends DynamoDbConditionExpressionBaseVisitor { /** {@inheritDoc} */ diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbReflectionHelper.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbReflectionHelper.java index 1a2e8792b0..c042178e00 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbReflectionHelper.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbReflectionHelper.java @@ -7,9 +7,19 @@ */ public final class DynamoDbReflectionHelper { + /** + * Utility class, no instances. + */ private DynamoDbReflectionHelper() { } + /** + * Invokes a no-argument method on the target object. + * + * @param target target object + * @param methodName method name to invoke + * @return invocation result or {@code null} on errors + */ public static Object invokeNoArg(Object target, String methodName) { if (target == null) { return null; @@ -22,6 +32,13 @@ public static Object invokeNoArg(Object target, String methodName) { } } + /** + * Invokes a no-argument method and returns it only when boolean. + * + * @param target target object + * @param methodName method name to invoke + * @return boolean result or {@code null} + */ public static Boolean invokeBooleanNoArg(Object target, String methodName) { Object value = invokeNoArg(target, methodName); return value instanceof Boolean ? (Boolean) value : null; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParser.java index 67004beb9d..60d0576d3b 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbRequestParser.java @@ -15,6 +15,9 @@ public class DynamoDbRequestParser { private final Map parsersByApiMethod; + /** + * Creates a request parser and registers supported DynamoDB API method parsers. + */ public DynamoDbRequestParser() { Map map = new LinkedHashMap<>(); registerParser(map, new QueryApiMethodParser()); @@ -28,7 +31,7 @@ public DynamoDbRequestParser() { } /** - * Entry-point parser used by the handler. + * Entry-point parser used by a future handler. * It routes a DynamoDB SDK request to the API-method parser and returns * one parsed condition tree per table name. * Unsupported operations intentionally yield an empty map. @@ -47,6 +50,12 @@ public Map parseByTable(Object request, DynamoDbOperatio return parsed == null ? Collections.emptyMap() : parsed; } + /** + * Registers one API-method parser and rejects duplicates. + * + * @param parsersByApiMethod parser registry by API method name + * @param parser parser instance to register + */ private static void registerParser(Map parsersByApiMethod, DynamoDbApiMethodParser parser) { DynamoDbOperationNames apiMethodName = parser.apiMethodName(); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/AndOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/AndOperation.java index f1794713da..00a29020ac 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/AndOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/AndOperation.java @@ -2,14 +2,27 @@ import java.util.List; +/** + * Logical AND operation over multiple DynamoDB query conditions. + */ public class AndOperation extends QueryOperation { private final List conditions; + /** + * Creates an AND operation. + * + * @param conditions conditions to combine + */ public AndOperation(List conditions) { this.conditions = conditions; } + /** + * Returns the conditions combined by this AND operation. + * + * @return combined conditions + */ public List getConditions() { return conditions; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java index 043d6fa7de..d8ad45ebbc 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java @@ -1,10 +1,19 @@ package org.evomaster.client.java.controller.dynamodb.operations; +/** + * DynamoDB {@code begins_with(path, value)} predicate operation. + */ public class BeginsWithOperation extends QueryOperation { private final String fieldName; private final Object prefix; + /** + * Creates a begins-with operation. + * + * @param fieldName attribute path + * @param prefix expected prefix + */ public BeginsWithOperation(String fieldName, Object prefix) { this.fieldName = fieldName; this.prefix = prefix; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java index 839db62eca..81bb200a11 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java @@ -1,11 +1,21 @@ package org.evomaster.client.java.controller.dynamodb.operations; +/** + * DynamoDB {@code path BETWEEN lower AND upper} predicate operation. + */ public class BetweenOperation extends QueryOperation { private final String fieldName; private final Object lowerBound; private final Object upperBound; + /** + * Creates a BETWEEN operation. + * + * @param fieldName attribute path + * @param lowerBound lower bound value + * @param upperBound upper bound value + */ public BetweenOperation(String fieldName, Object lowerBound, Object upperBound) { this.fieldName = fieldName; this.lowerBound = lowerBound; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java index 7bc35b5633..5b7574c6b5 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java @@ -1,10 +1,19 @@ package org.evomaster.client.java.controller.dynamodb.operations; +/** + * DynamoDB {@code contains(path, value)} predicate operation. + */ public class ContainsOperation extends QueryOperation { private final String fieldName; private final Object expectedValue; + /** + * Creates a contains operation. + * + * @param fieldName attribute path + * @param expectedValue expected contained value + */ public ContainsOperation(String fieldName, Object expectedValue) { this.fieldName = fieldName; this.expectedValue = expectedValue; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java index 33bf30a4f8..50b428a192 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java @@ -1,11 +1,21 @@ package org.evomaster.client.java.controller.dynamodb.operations; +/** + * DynamoDB existence predicate operation for {@code attribute_exists} and + * {@code attribute_not_exists}. + */ public class ExistsOperation extends QueryOperation { private final String fieldName; //true = exists, false = not exists private final boolean exists; + /** + * Creates an existence operation. + * + * @param fieldName attribute path + * @param exists {@code true} for exists, {@code false} for not-exists + */ public ExistsOperation(String fieldName, boolean exists) { this.fieldName = fieldName; this.exists = exists; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java index 3b3f444f2b..473a685dd5 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java @@ -2,11 +2,22 @@ import java.util.List; +/** + * DynamoDB {@code path IN (...)} predicate operation. + * + * @param value type + */ public class InOperation extends QueryOperation { private final String fieldName; private final List values; + /** + * Creates an IN operation. + * + * @param fieldName attribute path + * @param values candidate values + */ public InOperation(String fieldName, List values) { this.fieldName = fieldName; this.values = values; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java index 075a6060a1..e9e8403f83 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java @@ -1,9 +1,17 @@ package org.evomaster.client.java.controller.dynamodb.operations; +/** + * Logical NOT operation over one DynamoDB query condition. + */ public class NotOperation extends QueryOperation { private final QueryOperation condition; + /** + * Creates a NOT operation. + * + * @param condition condition to negate + */ public NotOperation(QueryOperation condition) { this.condition = condition; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/OrOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/OrOperation.java index de66cdc773..0f9eb05460 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/OrOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/OrOperation.java @@ -2,14 +2,27 @@ import java.util.List; +/** + * Logical OR operation over multiple DynamoDB query conditions. + */ public class OrOperation extends QueryOperation { private final List conditions; + /** + * Creates an OR operation. + * + * @param conditions conditions to combine + */ public OrOperation(List conditions) { this.conditions = conditions; } + /** + * Returns the conditions combined by this OR operation. + * + * @return combined conditions + */ public List getConditions() { return conditions; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java index a184d19695..6d94b3d3fc 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java @@ -2,12 +2,22 @@ import org.evomaster.client.java.controller.dynamodb.DynamoDbComparisonType; +/** + * DynamoDB {@code size(path) comparator value} predicate operation. + */ public class SizeOperation extends QueryOperation { private final String fieldName; private final DynamoDbComparisonType comparator; private final Object expectedValue; + /** + * Creates a size operation. + * + * @param fieldName attribute path + * @param comparator comparison operator + * @param expectedValue expected value + */ public SizeOperation(String fieldName, DynamoDbComparisonType comparator, Object expectedValue) { this.fieldName = fieldName; this.comparator = comparator; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java index fa1210de0f..c66adc1b59 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java @@ -1,10 +1,19 @@ package org.evomaster.client.java.controller.dynamodb.operations; +/** + * DynamoDB {@code attribute_type(path, type)} predicate operation. + */ public class TypeOperation extends QueryOperation { private final String fieldName; private final String expectedType; + /** + * Creates a type operation. + * + * @param fieldName attribute path + * @param expectedType expected DynamoDB type token + */ public TypeOperation(String fieldName, String expectedType) { this.fieldName = fieldName; this.expectedType = expectedType; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java index da752a7442..73d5f5d6f6 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java @@ -2,11 +2,22 @@ import org.evomaster.client.java.controller.dynamodb.operations.QueryOperation; +/** + * Base class for comparison operations over a field and value. + * + * @param value type + */ public abstract class ComparisonOperation extends QueryOperation { private final String fieldName; private final V value; + /** + * Creates a comparison operation. + * + * @param fieldName field name or path + * @param value comparison value + */ ComparisonOperation(String fieldName, V value) { this.fieldName = fieldName; this.value = value; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java index 785cb179d8..8bfe951c15 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java @@ -1,7 +1,18 @@ package org.evomaster.client.java.controller.dynamodb.operations.comparison; +/** + * Equality comparison operation ({@code =}). + * + * @param value type + */ public class EqualsOperation extends ComparisonOperation { + /** + * Creates an equality comparison operation. + * + * @param fieldName field name or path + * @param value comparison value + */ public EqualsOperation(String fieldName, V value) { super(fieldName, value); } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java index c0fb797809..e4dcc8c501 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java @@ -1,7 +1,18 @@ package org.evomaster.client.java.controller.dynamodb.operations.comparison; +/** + * Greater-than-or-equals comparison operation ({@code >=}). + * + * @param value type + */ public class GreaterThanEqualsOperation extends ComparisonOperation { + /** + * Creates a greater-than-or-equals comparison operation. + * + * @param fieldName field name or path + * @param value comparison value + */ public GreaterThanEqualsOperation(String fieldName, V value) { super(fieldName, value); } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java index dc3ca3d79e..35afe268c8 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java @@ -1,7 +1,18 @@ package org.evomaster.client.java.controller.dynamodb.operations.comparison; +/** + * Greater-than comparison operation ({@code >}). + * + * @param value type + */ public class GreaterThanOperation extends ComparisonOperation { + /** + * Creates a greater-than comparison operation. + * + * @param fieldName field name or path + * @param value comparison value + */ public GreaterThanOperation(String fieldName, V value) { super(fieldName, value); } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java index ee1bacc5e9..310ea7276b 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java @@ -1,7 +1,18 @@ package org.evomaster.client.java.controller.dynamodb.operations.comparison; +/** + * Less-than-or-equals comparison operation ({@code <=}). + * + * @param value type + */ public class LessThanEqualsOperation extends ComparisonOperation { + /** + * Creates a less-than-or-equals comparison operation. + * + * @param fieldName field name or path + * @param value comparison value + */ public LessThanEqualsOperation(String fieldName, V value) { super(fieldName, value); } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java index 8e2db85375..ab4c16f6df 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java @@ -1,7 +1,18 @@ package org.evomaster.client.java.controller.dynamodb.operations.comparison; +/** + * Less-than comparison operation ({@code <}). + * + * @param value type + */ public class LessThanOperation extends ComparisonOperation { + /** + * Creates a less-than comparison operation. + * + * @param fieldName field name or path + * @param value comparison value + */ public LessThanOperation(String fieldName, V value) { super(fieldName, value); } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java index b2a5e85312..9da15b5b7f 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java @@ -1,7 +1,18 @@ package org.evomaster.client.java.controller.dynamodb.operations.comparison; +/** + * Inequality comparison operation ({@code <>}). + * + * @param value type + */ public class NotEqualsOperation extends ComparisonOperation { + /** + * Creates an inequality comparison operation. + * + * @param fieldName field name or path + * @param value comparison value + */ public NotEqualsOperation(String fieldName, V value) { super(fieldName, value); } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/BatchGetItemApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/BatchGetItemApiMethodParser.java index a978665848..70b12cbd3a 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/BatchGetItemApiMethodParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/BatchGetItemApiMethodParser.java @@ -9,13 +9,22 @@ import static org.evomaster.client.java.controller.dynamodb.DynamoDbReflectionHelper.invokeNoArg; +/** + * Parser for DynamoDB {@code BatchGetItem} requests. + */ public class BatchGetItemApiMethodParser extends DynamoDbBaseApiMethodParser { + /** + * {@inheritDoc} + */ @Override public DynamoDbOperationNames apiMethodName() { return DynamoDbOperationNames.BATCH_GET_ITEM; } + /** + * {@inheritDoc} + */ @Override @SuppressWarnings("unchecked") public Map parseRequest(Object request) { @@ -55,6 +64,12 @@ public Map parseRequest(Object request) { return result; } + /** + * Combines key conditions with OR semantics. + * + * @param conditions per-key conditions + * @return combined operation + */ private QueryOperation combineWithOr(List conditions) { return combine(conditions, OrOperation::new); } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DeleteItemApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DeleteItemApiMethodParser.java index ef3d2006a5..63e8a553ce 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DeleteItemApiMethodParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DeleteItemApiMethodParser.java @@ -2,13 +2,22 @@ import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; +/** + * Parser for DynamoDB {@code DeleteItem} requests. + */ public class DeleteItemApiMethodParser extends WriteMethodParser { + /** + * {@inheritDoc} + */ @Override public DynamoDbOperationNames apiMethodName() { return DynamoDbOperationNames.DELETE_ITEM; } + /** + * {@inheritDoc} + */ @Override protected boolean requiresKeyCondition() { return true; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbApiMethodParser.java index ea2ef5e821..be18a58124 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbApiMethodParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbApiMethodParser.java @@ -10,7 +10,18 @@ */ public interface DynamoDbApiMethodParser { + /** + * Returns the DynamoDB API method handled by this parser. + * + * @return API method identifier + */ DynamoDbOperationNames apiMethodName(); + /** + * Parses one request object into table-specific query operations. + * + * @param request DynamoDB request object + * @return a map of parsed operations by table name + */ Map parseRequest(Object request); } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbBaseApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbBaseApiMethodParser.java index 91f206dc06..9637148444 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbBaseApiMethodParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/DynamoDbBaseApiMethodParser.java @@ -11,6 +11,9 @@ import static org.evomaster.client.java.controller.dynamodb.DynamoDbReflectionHelper.invokeNoArg; +/** + * Base class for DynamoDB SDK requests parser. Contains shared utilities. + */ abstract class DynamoDbBaseApiMethodParser implements DynamoDbApiMethodParser { // All these constants are used to invoke DDB API methods by reflection, do not change. @@ -24,6 +27,14 @@ abstract class DynamoDbBaseApiMethodParser implements DynamoDbApiMethodParser { protected static final String METHOD_EXPRESSION_ATTRIBUTE_NAMES = "expressionAttributeNames"; protected static final String METHOD_EXPRESSION_ATTRIBUTE_VALUES = "expressionAttributeValues"; + /** + * Parses a DynamoDB expression string into a query operation. + * + * @param expression expression string + * @param expressionAttributeNames name placeholders map + * @param expressionAttributeValues value placeholders map + * @return parsed operation, or {@code null} + */ protected QueryOperation parseExpression( String expression, Map expressionAttributeNames, @@ -31,11 +42,23 @@ protected QueryOperation parseExpression( return new DynamoDbExpressionParser().parse(expression, expressionAttributeNames, expressionAttributeValues); } + /** + * Parses key equality conditions from request key fields. + * + * @param request request object + * @return parsed key condition operation + */ protected QueryOperation parseKeyCondition(Object request) { Object keyObj = invokeNoArg(request, METHOD_KEY); return buildEqualsFromMap(DynamoDbAttributeValueHelper.toPlainMap(keyObj)); } + /** + * Builds equality operations from a field/value map. + * + * @param values field/value map + * @return combined equality operation, or {@code null} + */ protected QueryOperation buildEqualsFromMap(Map values) { if (values == null || values.isEmpty()) { return null; @@ -46,14 +69,34 @@ protected QueryOperation buildEqualsFromMap(Map values) { return combineWithAnd(conditions); } + /** + * Combines two operations with AND semantics. + * + * @param left left operation + * @param right right operation + * @return combined operation + */ protected QueryOperation combineWithAnd(QueryOperation left, QueryOperation right) { return combineWithAnd(Arrays.asList(left, right)); } + /** + * Combines a list of operations with AND semantics. + * + * @param conditions conditions to combine + * @return combined operation + */ protected QueryOperation combineWithAnd(List conditions) { return combine(conditions, AndOperation::new); } + /** + * Combines a list of operations with a provided composite builder, skipping null entries. + * + * @param conditions operations to combine + * @param compositeBuilder builder for composite operation + * @return combined operation, one operation, or {@code null} + */ protected QueryOperation combine(List conditions, Function, QueryOperation> compositeBuilder) { List filtered = new ArrayList<>(); for (QueryOperation operation : conditions) { @@ -71,6 +114,12 @@ protected QueryOperation combine(List conditions, Function readNameMap(Object request) { Object raw = invokeNoArg(request, METHOD_EXPRESSION_ATTRIBUTE_NAMES); if (!(raw instanceof Map)) { @@ -86,16 +135,35 @@ protected Map readNameMap(Object request) { return result; } + /** + * Reads and converts expression attribute values from request object. + * + * @param request request object + * @return normalized value map + */ protected Map readValueMap(Object request) { Object raw = invokeNoArg(request, METHOD_EXPRESSION_ATTRIBUTE_VALUES); return DynamoDbAttributeValueHelper.toPlainMap(raw); } + /** + * Reads a string-like value from request object via reflection. + * + * @param target target object + * @param methodName accessor method name + * @return string value, or {@code null} + */ protected String readString(Object target, String methodName) { Object value = invokeNoArg(target, methodName); return value == null ? null : String.valueOf(value); } + /** + * Reads and validates the table name from the request. + * + * @param request request object + * @return table name, or {@code null} if blank/absent + */ protected String readValidTableName(Object request) { String tableName = readString(request, METHOD_TABLE_NAME); if (tableName == null || tableName.trim().isEmpty()) { @@ -104,6 +172,13 @@ protected String readValidTableName(Object request) { return tableName; } + /** + * Builds a singleton result map for one table/operation pair. + * + * @param tableName table name + * @param operation parsed operation + * @return singleton map or empty map when invalid + */ protected Map singleTableResult(String tableName, QueryOperation operation) { if (tableName == null || operation == null) { return Collections.emptyMap(); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/GetItemApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/GetItemApiMethodParser.java index 66f7dcce5f..e6dac06531 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/GetItemApiMethodParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/GetItemApiMethodParser.java @@ -5,13 +5,22 @@ import java.util.Map; +/** + * Parser for DynamoDB {@code GetItem} requests. + */ public class GetItemApiMethodParser extends DynamoDbBaseApiMethodParser { + /** + * {@inheritDoc} + */ @Override public DynamoDbOperationNames apiMethodName() { return DynamoDbOperationNames.GET_ITEM; } + /** + * {@inheritDoc} + */ @Override public Map parseRequest(Object request) { String tableName = readValidTableName(request); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/PutItemApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/PutItemApiMethodParser.java index 6f5889520b..35619e3c72 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/PutItemApiMethodParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/PutItemApiMethodParser.java @@ -2,13 +2,22 @@ import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; +/** + * Parser for DynamoDB {@code PutItem} requests. + */ public class PutItemApiMethodParser extends WriteMethodParser { + /** + * {@inheritDoc} + */ @Override public DynamoDbOperationNames apiMethodName() { return DynamoDbOperationNames.PUT_ITEM; } + /** + * {@inheritDoc} + */ @Override protected boolean requiresKeyCondition() { return false; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/QueryApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/QueryApiMethodParser.java index 83d113927f..2003448fe9 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/QueryApiMethodParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/QueryApiMethodParser.java @@ -6,13 +6,22 @@ import java.util.Collections; import java.util.Map; +/** + * Parser for DynamoDB {@code Query} requests. + */ public class QueryApiMethodParser extends DynamoDbBaseApiMethodParser { + /** + * {@inheritDoc} + */ @Override public DynamoDbOperationNames apiMethodName() { return DynamoDbOperationNames.QUERY; } + /** + * {@inheritDoc} + */ @Override public Map parseRequest(Object request) { String tableName = readValidTableName(request); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/ScanApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/ScanApiMethodParser.java index af760f1159..9da73906b0 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/ScanApiMethodParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/ScanApiMethodParser.java @@ -6,13 +6,22 @@ import java.util.Collections; import java.util.Map; +/** + * Parser for DynamoDB {@code Scan} requests. + */ public class ScanApiMethodParser extends DynamoDbBaseApiMethodParser { + /** + * {@inheritDoc} + */ @Override public DynamoDbOperationNames apiMethodName() { return DynamoDbOperationNames.SCAN; } + /** + * {@inheritDoc} + */ @Override public Map parseRequest(Object request) { String tableName = readValidTableName(request); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/UpdateItemApiMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/UpdateItemApiMethodParser.java index e212909048..e215541607 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/UpdateItemApiMethodParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/UpdateItemApiMethodParser.java @@ -2,13 +2,22 @@ import org.evomaster.client.java.instrumentation.DynamoDbOperationNames; +/** + * Parser for DynamoDB {@code UpdateItem} requests. + */ public class UpdateItemApiMethodParser extends WriteMethodParser { + /** + * {@inheritDoc} + */ @Override public DynamoDbOperationNames apiMethodName() { return DynamoDbOperationNames.UPDATE_ITEM; } + /** + * {@inheritDoc} + */ @Override protected boolean requiresKeyCondition() { return true; diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/WriteMethodParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/WriteMethodParser.java index f4be7e131a..49bb00422d 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/WriteMethodParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/parsers/WriteMethodParser.java @@ -5,10 +5,21 @@ import java.util.Collections; import java.util.Map; +/** + * Base parser for write APIs with optional key and condition expressions. + */ abstract class WriteMethodParser extends DynamoDbBaseApiMethodParser { + /** + * Indicates whether the write method requires key conditions. + * + * @return {@code true} if key conditions are required + */ protected abstract boolean requiresKeyCondition(); + /** + * {@inheritDoc} + */ @Override public final Map parseRequest(Object request) { String tableName = readValidTableName(request); From 22f85da2f98eb44e57866684369ec46c2287a394 Mon Sep 17 00:00:00 2001 From: aschenzle Date: Fri, 24 Apr 2026 18:01:12 -0700 Subject: [PATCH 3/3] Adding Javadoc to all methods. Removing unnecessary function. --- .../dynamodb/DynamoDbComparisonType.java | 2 +- .../dynamodb/DynamoDbExpressionParser.java | 34 +++++++------------ .../operations/BeginsWithOperation.java | 8 ++++- .../dynamodb/operations/BetweenOperation.java | 11 +++++- .../operations/ContainsOperation.java | 8 ++++- .../dynamodb/operations/ExistsOperation.java | 8 ++++- .../dynamodb/operations/InOperation.java | 8 ++++- .../dynamodb/operations/NotOperation.java | 3 ++ .../dynamodb/operations/SizeOperation.java | 11 +++++- .../dynamodb/operations/TypeOperation.java | 8 ++++- .../comparison/ComparisonOperation.java | 8 ++++- .../comparison/EqualsOperation.java | 2 +- .../GreaterThanEqualsOperation.java | 2 +- .../comparison/GreaterThanOperation.java | 2 +- .../comparison/LessThanEqualsOperation.java | 2 +- .../comparison/LessThanOperation.java | 2 +- .../comparison/NotEqualsOperation.java | 2 +- 17 files changed, 84 insertions(+), 37 deletions(-) diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java index f9d4860a4f..0a719d52d9 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbComparisonType.java @@ -44,7 +44,7 @@ public static DynamoDbComparisonType fromToken(String token) { /** * Creates a comparison operation instance for this comparison type. * - * @param fieldName compared field name/path + * @param fieldName field name coming from DynamoDB expression/condition * @param value comparison value * @return concrete comparison operation */ diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java index 1b943aa374..468a0867da 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/DynamoDbExpressionParser.java @@ -77,20 +77,10 @@ private void prepareParser(Parser parser) { } /** - * Parses and resolves a field path from Parser context. + * Resolves expression-attribute-name aliases in a dotted field name. * - * @param pathContext the parsed path context - * @return resolved field path with attribute-name aliases expanded - */ - private String parsePath(DynamoDbConditionExpressionParser.PathContext pathContext) { - return parseFieldName(pathContext.getText()); - } - - /** - * Resolves expression-attribute-name aliases in a dotted field path. - * - * @param token raw field token from the expression - * @return resolved field path + * @param token raw field token coming from DynamoDB expression/condition + * @return resolved field name coming from DynamoDB expression/condition */ private String parseFieldName(String token) { String[] chunks = token.split("\\."); @@ -274,38 +264,38 @@ public QueryOperation visitPredicatePrimary(DynamoDbConditionExpressionParser.Pr /** {@inheritDoc} */ @Override public QueryOperation visitAttributeExistsPredicate(DynamoDbConditionExpressionParser.AttributeExistsPredicateContext ctx) { - return new ExistsOperation(parsePath(ctx.path()), true); + return new ExistsOperation(parseFieldName(ctx.path().getText()), true); } /** {@inheritDoc} */ @Override public QueryOperation visitAttributeNotExistsPredicate(DynamoDbConditionExpressionParser.AttributeNotExistsPredicateContext ctx) { - return new ExistsOperation(parsePath(ctx.path()), false); + return new ExistsOperation(parseFieldName(ctx.path().getText()), false); } /** {@inheritDoc} */ @Override public QueryOperation visitAttributeTypePredicate(DynamoDbConditionExpressionParser.AttributeTypePredicateContext ctx) { Object expectedType = parseValue(ctx.value()); - return new TypeOperation(parsePath(ctx.path()), expectedType == null ? null : String.valueOf(expectedType)); + return new TypeOperation(parseFieldName(ctx.path().getText()), expectedType == null ? null : String.valueOf(expectedType)); } /** {@inheritDoc} */ @Override public QueryOperation visitBeginsWithPredicate(DynamoDbConditionExpressionParser.BeginsWithPredicateContext ctx) { - return new BeginsWithOperation(parsePath(ctx.path()), parseValue(ctx.value())); + return new BeginsWithOperation(parseFieldName(ctx.path().getText()), parseValue(ctx.value())); } /** {@inheritDoc} */ @Override public QueryOperation visitContainsPredicate(DynamoDbConditionExpressionParser.ContainsPredicateContext ctx) { - return new ContainsOperation(parsePath(ctx.path()), parseValue(ctx.value())); + return new ContainsOperation(parseFieldName(ctx.path().getText()), parseValue(ctx.value())); } /** {@inheritDoc} */ @Override public QueryOperation visitSizePredicate(DynamoDbConditionExpressionParser.SizePredicateContext ctx) { - String field = parsePath(ctx.path()); + String field = parseFieldName(ctx.path().getText()); DynamoDbComparisonType comparator = DynamoDbComparisonType.fromToken(ctx.comparator().getText()); Object expectedValue = parseValue(ctx.value()); return new SizeOperation(field, comparator, expectedValue); @@ -314,7 +304,7 @@ public QueryOperation visitSizePredicate(DynamoDbConditionExpressionParser.SizeP /** {@inheritDoc} */ @Override public QueryOperation visitBetweenPredicate(DynamoDbConditionExpressionParser.BetweenPredicateContext ctx) { - String field = parsePath(ctx.path()); + String field = parseFieldName(ctx.path().getText()); Object lower = parseValue(ctx.value(0)); Object upper = parseValue(ctx.value(1)); return new BetweenOperation(field, lower, upper); @@ -327,13 +317,13 @@ public QueryOperation visitInPredicate(DynamoDbConditionExpressionParser.InPredi for (DynamoDbConditionExpressionParser.ValueContext valueContext : ctx.value()) { values.add(parseValue(valueContext)); } - return new InOperation<>(parsePath(ctx.path()), values); + return new InOperation<>(parseFieldName(ctx.path().getText()), values); } /** {@inheritDoc} */ @Override public QueryOperation visitComparisonPredicate(DynamoDbConditionExpressionParser.ComparisonPredicateContext ctx) { - String field = parsePath(ctx.path()); + String field = parseFieldName(ctx.path().getText()); DynamoDbComparisonType comparator = DynamoDbComparisonType.fromToken(ctx.comparator().getText()); Object value = parseValue(ctx.value()); return comparator.toOperation(field, value); diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java index d8ad45ebbc..5e5bc8ce22 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BeginsWithOperation.java @@ -11,7 +11,7 @@ public class BeginsWithOperation extends QueryOperation { /** * Creates a begins-with operation. * - * @param fieldName attribute path + * @param fieldName field name coming from DynamoDB expression/condition * @param prefix expected prefix */ public BeginsWithOperation(String fieldName, Object prefix) { @@ -19,10 +19,16 @@ public BeginsWithOperation(String fieldName, Object prefix) { this.prefix = prefix; } + /** + * @return field name coming from DynamoDB expression/condition + */ public String getFieldName() { return fieldName; } + /** + * @return expected prefix + */ public Object getPrefix() { return prefix; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java index 81bb200a11..3e60a9c4ed 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/BetweenOperation.java @@ -12,7 +12,7 @@ public class BetweenOperation extends QueryOperation { /** * Creates a BETWEEN operation. * - * @param fieldName attribute path + * @param fieldName field name coming from DynamoDB expression/condition * @param lowerBound lower bound value * @param upperBound upper bound value */ @@ -22,14 +22,23 @@ public BetweenOperation(String fieldName, Object lowerBound, Object upperBound) this.upperBound = upperBound; } + /** + * @return field name coming from DynamoDB expression/condition + */ public String getFieldName() { return fieldName; } + /** + * @return lower bound value + */ public Object getLowerBound() { return lowerBound; } + /** + * @return upper bound value + */ public Object getUpperBound() { return upperBound; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java index 5b7574c6b5..9580d026c2 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ContainsOperation.java @@ -11,7 +11,7 @@ public class ContainsOperation extends QueryOperation { /** * Creates a contains operation. * - * @param fieldName attribute path + * @param fieldName field name coming from DynamoDB expression/condition * @param expectedValue expected contained value */ public ContainsOperation(String fieldName, Object expectedValue) { @@ -19,10 +19,16 @@ public ContainsOperation(String fieldName, Object expectedValue) { this.expectedValue = expectedValue; } + /** + * @return field name coming from DynamoDB expression/condition + */ public String getFieldName() { return fieldName; } + /** + * @return expected contained value + */ public Object getExpectedValue() { return expectedValue; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java index 50b428a192..e45cc3e333 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/ExistsOperation.java @@ -13,7 +13,7 @@ public class ExistsOperation extends QueryOperation { /** * Creates an existence operation. * - * @param fieldName attribute path + * @param fieldName field name coming from DynamoDB expression/condition * @param exists {@code true} for exists, {@code false} for not-exists */ public ExistsOperation(String fieldName, boolean exists) { @@ -21,10 +21,16 @@ public ExistsOperation(String fieldName, boolean exists) { this.exists = exists; } + /** + * @return field name coming from DynamoDB expression/condition + */ public String getFieldName() { return fieldName; } + /** + * @return {@code true} when existence is required, {@code false} otherwise + */ public boolean isExists() { return exists; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java index 473a685dd5..df230f0fd9 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/InOperation.java @@ -15,7 +15,7 @@ public class InOperation extends QueryOperation { /** * Creates an IN operation. * - * @param fieldName attribute path + * @param fieldName field name coming from DynamoDB expression/condition * @param values candidate values */ public InOperation(String fieldName, List values) { @@ -23,10 +23,16 @@ public InOperation(String fieldName, List values) { this.values = values; } + /** + * @return field name coming from DynamoDB expression/condition + */ public String getFieldName() { return fieldName; } + /** + * @return candidate values + */ public List getValues() { return values; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java index e9e8403f83..3be8bc95dd 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/NotOperation.java @@ -16,6 +16,9 @@ public NotOperation(QueryOperation condition) { this.condition = condition; } + /** + * @return negated condition + */ public QueryOperation getCondition() { return condition; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java index 6d94b3d3fc..eac67f0c1c 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/SizeOperation.java @@ -14,7 +14,7 @@ public class SizeOperation extends QueryOperation { /** * Creates a size operation. * - * @param fieldName attribute path + * @param fieldName field name coming from DynamoDB expression/condition * @param comparator comparison operator * @param expectedValue expected value */ @@ -24,14 +24,23 @@ public SizeOperation(String fieldName, DynamoDbComparisonType comparator, Object this.expectedValue = expectedValue; } + /** + * @return field name coming from DynamoDB expression/condition + */ public String getFieldName() { return fieldName; } + /** + * @return comparison operator + */ public DynamoDbComparisonType getComparator() { return comparator; } + /** + * @return expected value + */ public Object getExpectedValue() { return expectedValue; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java index c66adc1b59..8dbf35e230 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/TypeOperation.java @@ -11,7 +11,7 @@ public class TypeOperation extends QueryOperation { /** * Creates a type operation. * - * @param fieldName attribute path + * @param fieldName field name coming from DynamoDB expression/condition * @param expectedType expected DynamoDB type token */ public TypeOperation(String fieldName, String expectedType) { @@ -19,10 +19,16 @@ public TypeOperation(String fieldName, String expectedType) { this.expectedType = expectedType; } + /** + * @return field name coming from DynamoDB expression/condition + */ public String getFieldName() { return fieldName; } + /** + * @return expected DynamoDB type token + */ public String getExpectedType() { return expectedType; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java index 73d5f5d6f6..67aa85090e 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/ComparisonOperation.java @@ -15,7 +15,7 @@ public abstract class ComparisonOperation extends QueryOperation { /** * Creates a comparison operation. * - * @param fieldName field name or path + * @param fieldName field name coming from DynamoDB expression/condition * @param value comparison value */ ComparisonOperation(String fieldName, V value) { @@ -23,10 +23,16 @@ public abstract class ComparisonOperation extends QueryOperation { this.value = value; } + /** + * @return field name coming from DynamoDB expression/condition + */ public String getFieldName() { return fieldName; } + /** + * @return comparison value + */ public V getValue() { return value; } diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java index 8bfe951c15..8fe7b06036 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/EqualsOperation.java @@ -10,7 +10,7 @@ public class EqualsOperation extends ComparisonOperation { /** * Creates an equality comparison operation. * - * @param fieldName field name or path + * @param fieldName field name coming from DynamoDB expression/condition * @param value comparison value */ public EqualsOperation(String fieldName, V value) { diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java index e4dcc8c501..c042f63f10 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanEqualsOperation.java @@ -10,7 +10,7 @@ public class GreaterThanEqualsOperation extends ComparisonOperation { /** * Creates a greater-than-or-equals comparison operation. * - * @param fieldName field name or path + * @param fieldName field name coming from DynamoDB expression/condition * @param value comparison value */ public GreaterThanEqualsOperation(String fieldName, V value) { diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java index 35afe268c8..6ccdf43572 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/GreaterThanOperation.java @@ -10,7 +10,7 @@ public class GreaterThanOperation extends ComparisonOperation { /** * Creates a greater-than comparison operation. * - * @param fieldName field name or path + * @param fieldName field name coming from DynamoDB expression/condition * @param value comparison value */ public GreaterThanOperation(String fieldName, V value) { diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java index 310ea7276b..9ca900ae1d 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanEqualsOperation.java @@ -10,7 +10,7 @@ public class LessThanEqualsOperation extends ComparisonOperation { /** * Creates a less-than-or-equals comparison operation. * - * @param fieldName field name or path + * @param fieldName field name coming from DynamoDB expression/condition * @param value comparison value */ public LessThanEqualsOperation(String fieldName, V value) { diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java index ab4c16f6df..8288cbb170 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/LessThanOperation.java @@ -10,7 +10,7 @@ public class LessThanOperation extends ComparisonOperation { /** * Creates a less-than comparison operation. * - * @param fieldName field name or path + * @param fieldName field name coming from DynamoDB expression/condition * @param value comparison value */ public LessThanOperation(String fieldName, V value) { diff --git a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java index 9da15b5b7f..42eb51e4cb 100644 --- a/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java +++ b/client-java/controller/src/main/java/org/evomaster/client/java/controller/dynamodb/operations/comparison/NotEqualsOperation.java @@ -10,7 +10,7 @@ public class NotEqualsOperation extends ComparisonOperation { /** * Creates an inequality comparison operation. * - * @param fieldName field name or path + * @param fieldName field name coming from DynamoDB expression/condition * @param value comparison value */ public NotEqualsOperation(String fieldName, V value) {