or run

tessl search
Log in

Version

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/io.cucumber/cucumber-expressions@19.0.x

docs

examples

edge-cases.mdreal-world-scenarios.md
index.md
tile.json

tessl/maven-io-cucumber--cucumber-expressions

tessl install tessl/maven-io-cucumber--cucumber-expressions@19.0.0

Cucumber Expressions are simple patterns for matching Step Definitions with Gherkin steps

edge-cases.mddocs/examples/

Edge Cases and Advanced Scenarios

This document covers corner cases, unusual inputs, and advanced scenarios that require special handling.

Empty and Null Handling

Empty Expressions

Expression expr = factory.createExpression("");
Optional<List<Argument<?>>> match1 = expr.match("");
// match1.isPresent() = true, arguments = [] (empty list)

Optional<List<Argument<?>>> match2 = expr.match("any text");
// match2.isPresent() = false

Null Values in Transformers

// Transformer that handles null gracefully
Transformer<String> nullSafeTransformer = (String arg) -> {
    return arg == null ? "default" : arg.trim();
};

ParameterType<String> nullableType = new ParameterType<>(
    "nullable",
    ".*",
    String.class,
    nullSafeTransformer
);

// Transformer that returns null
Transformer<Optional<String>> optionalTransformer = (String arg) -> {
    return (arg == null || arg.isEmpty()) ? null : arg;
};

Empty Parameter Matches

// String parameter with empty quotes
Expression expr = factory.createExpression("Value is {string}");
Optional<List<Argument<?>>> match = expr.match("Value is \"\"");
// match.isPresent() = true, value = "" (empty string)

// Anonymous parameter
Expression expr2 = factory.createExpression("Value is {}");
Optional<List<Argument<?>>> match2 = expr2.match("Value is ");
// match2.isPresent() = false (doesn't match trailing space alone)

Whitespace Handling

Significant Whitespace

// Whitespace in expression is significant
Expression expr = factory.createExpression("I have {int} cucumbers");

expr.match("I have 42 cucumbers");    // ✓ Matches
expr.match("I have  42  cucumbers");  // ✗ Doesn't match (extra spaces)
expr.match("I have 42cucumbers");     // ✗ Doesn't match (missing space)

Tab and Newline Characters

// Tabs are not equivalent to spaces
Expression expr = factory.createExpression("Column\t{int}");
expr.match("Column\t42");   // ✓ Matches (tab character)
expr.match("Column 42");    // ✗ Doesn't match (space character)

// Newlines must be matched explicitly
Expression expr2 = factory.createExpression("Line1{string}Line2");
expr2.match("Line1\"text\nmore\"Line2");  // Depends on implementation

Variable Whitespace with Regex

// Use regex for flexible whitespace
Expression expr = factory.createExpression("^I\\s+have\\s+(\\d+)\\s+cucumbers$");
expr.match("I  have  42  cucumbers");    // ✓ Matches
expr.match("I\thave\t42\tcucumbers");   // ✓ Matches

Case Sensitivity

Default Case-Sensitive Matching

Expression expr = factory.createExpression("I have {int} cucumbers");
expr.match("I have 42 cucumbers");  // ✓ Matches
expr.match("I HAVE 42 CUCUMBERS");  // ✗ Doesn't match
expr.match("i have 42 cucumbers");  // ✗ Doesn't match

Case-Insensitive Custom Types

// Case-insensitive color type
Transformer<Color> caseInsensitiveColor = (String s) -> {
    return Color.valueOf(s.toUpperCase());
};

ParameterType<Color> colorType = new ParameterType<>(
    "color",
    "(?i)red|blue|green",  // Case-insensitive regex
    Color.class,
    caseInsensitiveColor
);

Case-Insensitive Regex Mode

// Use (?i) flag for case-insensitive regex
Expression expr = factory.createExpression("^(?i)I have (\\d+) cucumbers$");
expr.match("I HAVE 42 CUCUMBERS");  // ✓ Matches
expr.match("i have 42 cucumbers");  // ✓ Matches

Unicode Support

Unicode Characters in Expressions

// Unicode characters supported in expressions
Expression expr = factory.createExpression("I have {int} 🥒");
Optional<List<Argument<?>>> match = expr.match("I have 42 🥒");
// ✓ Matches

// Emoji in parameter type names (not recommended but works)
ParameterType<String> emojiType = new ParameterType<>(
    "emoji",
    "[\\p{So}]+",
    String.class,
    s -> s
);

Expression expr2 = factory.createExpression("Reaction is {emoji}");
Optional<List<Argument<?>>> match2 = expr2.match("Reaction is 😀🎉");
// ✓ Matches

Unicode Position Handling

// Positions are based on Java char indices (UTF-16 code units)
String text = "I have 42 🥒";  // Emoji is 2 chars in UTF-16
Expression expr = factory.createExpression("I have {int} 🥒");
Optional<List<Argument<?>>> match = expr.match(text);

if (match.isPresent()) {
    Group group = match.get().get(0).getGroup();
    int start = group.getStart();  // Position in char indices
    int end = group.getEnd();
    
    // For proper Unicode handling with surrogate pairs:
    int codePointStart = text.codePointCount(0, start);
}

Non-ASCII Text

// Non-ASCII text in expressions and parameters
Expression expr = factory.createExpression("Usuario {word} tiene {int} años");
Optional<List<Argument<?>>> match = expr.match("Usuario José tiene 25 años");
// ✓ Matches

// Chinese characters
Expression expr2 = factory.createExpression("用户 {word} 有 {int} 个");
Optional<List<Argument<?>>> match2 = expr2.match("用户 张三 有 42 个");
// ✓ Matches

Special Characters

Anchors in Cucumber Expression Mode

// Anchors are literal text in Cucumber Expression mode
Expression expr = factory.createExpression("I have {int} cucumbers^");
expr.match("I have 42 cucumbers^");  // ✓ Matches (literal ^ at end)
expr.match("I have 42 cucumbers");   // ✗ Doesn't match (missing ^)

// To use regex anchors, must start with ^ or end with $
Expression regexExpr = factory.createExpression("^I have {int} cucumbers$");
// This creates a RegularExpression with proper anchors

Escaped Special Characters

// Escape special characters with backslash
Expression expr = factory.createExpression("Price is {float} \\$");
expr.match("Price is 99.99 $");  // ✓ Matches

// Characters that need escaping: \ { } ( ) /
Expression expr2 = factory.createExpression("Value \\{type\\} is {int}");
expr2.match("Value {type} is 42");  // ✓ Matches

Dots and Wildcards

// Dot is literal in Cucumber Expression mode
Expression expr = factory.createExpression("Version {int}.{int}.{int}");
expr.match("Version 1.2.3");  // ✓ Matches
expr.match("Version 1a2b3");  // ✗ Doesn't match (dots are required)

// In regex mode, dot matches any character
Expression regexExpr = factory.createExpression("^Version (\\d+).(\\d+).(\\d+)$");
regexExpr.match("Version 1a2b3");  // ✓ Matches (dots match any char)

Number Parsing Edge Cases

Locale-Specific Formats

// English locale: comma as thousands separator
ParameterTypeRegistry englishRegistry = new ParameterTypeRegistry(Locale.ENGLISH);
ExpressionFactory englishFactory = new ExpressionFactory(englishRegistry);
Expression expr = englishFactory.createExpression("Value is {int}");

expr.match("Value is 1,234");      // ✓ Matches, parses as 1234
expr.match("Value is 1.234");      // ✗ Doesn't match (period not valid for int)

// French locale: period as thousands separator
ParameterTypeRegistry frenchRegistry = new ParameterTypeRegistry(Locale.FRENCH);
ExpressionFactory frenchFactory = new ExpressionFactory(frenchRegistry);
Expression exprFr = frenchFactory.createExpression("Valeur est {int}");

exprFr.match("Valeur est 1.234");  // ✓ Matches, parses as 1234
exprFr.match("Valeur est 1,234");  // ✗ Doesn't match (comma is decimal sep in French)

Scientific Notation

Expression expr = factory.createExpression("Value is {double}");
expr.match("Value is 1.23e5");     // ✓ Matches, parses as 123000.0
expr.match("Value is 1.23E-5");    // ✓ Matches, parses as 0.0000123
expr.match("Value is 1.23e");      // ✗ Doesn't match (incomplete exponent)

Negative Zero

Expression expr = factory.createExpression("Value is {float}");
Optional<List<Argument<?>>> match = expr.match("Value is -0.0");
// ✓ Matches

if (match.isPresent()) {
    Float value = (Float) match.get().get(0).getValue();
    // value = -0.0 (negative zero is preserved)
    System.out.println(1.0f / value);  // Prints: -Infinity
}

Overflow and Underflow

// Integer overflow
Expression intExpr = factory.createExpression("Value is {int}");
Optional<List<Argument<?>>> match1 = intExpr.match("Value is 2147483648");
// Throws NumberFormatException during getValue() (exceeds Integer.MAX_VALUE)

// Use long or BigInteger for large numbers
Expression longExpr = factory.createExpression("Value is {long}");
Optional<List<Argument<?>>> match2 = longExpr.match("Value is 2147483648");
// ✓ Matches

Expression bigIntExpr = factory.createExpression("Value is {biginteger}");
Optional<List<Argument<?>>> match3 = bigIntExpr.match("Value is 999999999999999999999");
// ✓ Matches

Complex Regex Patterns

Nested Capture Groups

// Parameter type with nested capture groups
ParameterType<Coordinate> coordType = new ParameterType<>(
    "coord",
    "((\\d+),(\\d+))",  // Nested groups
    Coordinate.class,
    (String[] args) -> {
        // args[0] = full match "10,20"
        // args[1] = first capture "10"
        // args[2] = second capture "20"
        return new Coordinate(
            Integer.parseInt(args[1]),
            Integer.parseInt(args[2])
        );
    }
);

Non-Capturing Groups

// Non-capturing group (?:...) doesn't create argument
Expression expr = factory.createExpression("^(?:GET|POST) /api/(\\d+)$");
Optional<List<Argument<?>>> match = expr.match("GET /api/123");

if (match.isPresent()) {
    // Only one argument from the (\\d+) group
    Integer id = (Integer) match.get().get(0).getValue();
    // No argument for (?:GET|POST) non-capturing group
}

Lookahead and Lookbehind

// Positive lookahead
Expression expr = factory.createExpression("^Value (\\d+)(?= units)$");
expr.match("Value 42 units");  // ✓ Matches, extracts 42

// Negative lookahead
Expression expr2 = factory.createExpression("^Value (\\d+)(?! units)$");
expr2.match("Value 42 items");  // ✓ Matches
expr2.match("Value 42 units");  // ✗ Doesn't match

Thread Safety Edge Cases

Concurrent Registry Modification

// UNSAFE: Modifying registry during concurrent use
ParameterTypeRegistry registry = new ParameterTypeRegistry(Locale.ENGLISH);
ExpressionFactory factory = new ExpressionFactory(registry);

// Thread 1: Using factory
new Thread(() -> {
    Expression expr = factory.createExpression("I have {color} ball");
}).start();

// Thread 2: Modifying registry (RACE CONDITION!)
new Thread(() -> {
    registry.defineParameterType(colorType);  // ⚠️ NOT THREAD-SAFE
}).start();

// SAFE: Complete registration before concurrent use
registry.defineParameterType(colorType);  // Register first
// Now safe to use concurrently

Expression Sharing

// Expression instances are thread-safe
Expression expr = factory.createExpression("I have {int} cucumbers");

// Safe: Multiple threads matching same expression
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
    final int count = i;
    executor.submit(() -> {
        String text = "I have " + count + " cucumbers";
        Optional<List<Argument<?>>> match = expr.match(text);
        // Thread-safe operation
    });
}

Transformer Exception Handling

Checked Exceptions in Transformers

// Transformer that throws checked exception
Transformer<User> userTransformer = (String username) -> {
    User user = database.findUser(username);  // May throw SQLException
    if (user == null) {
        throw new UserNotFoundException(username);
    }
    return user;
};

// Exception is caught and re-thrown during getValue()
Expression expr = factory.createExpression("User {user} logs in");
Optional<List<Argument<?>>> match = expr.match("User john logs in");

if (match.isPresent()) {
    try {
        User user = (User) match.get().get(0).getValue();
    } catch (RuntimeException e) {
        Throwable cause = e.getCause();
        if (cause instanceof UserNotFoundException) {
            // Handle user not found
        }
    }
}

Validation in Transformers

// Multiple validation checks
Transformer<Email> emailTransformer = (String s) -> {
    if (s == null || s.isEmpty()) {
        throw new IllegalArgumentException("Email cannot be empty");
    }
    if (!s.contains("@")) {
        throw new IllegalArgumentException("Email must contain @");
    }
    if (!s.matches("[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}")) {
        throw new IllegalArgumentException("Invalid email format: " + s);
    }
    String domain = s.substring(s.indexOf('@') + 1);
    if (domain.equals("example.com")) {
        throw new IllegalArgumentException("Example.com domain not allowed");
    }
    return new Email(s);
};

Parameter Type Conflicts

Ambiguous Patterns

// Two types with same pattern
ParameterType<Integer> type1 = new ParameterType<>(
    "count", "\\d+", Integer.class, Integer::parseInt
);
ParameterType<String> type2 = new ParameterType<>(
    "number", "\\d+", String.class, s -> s
);

registry.defineParameterType(type1);
registry.defineParameterType(type2);

try {
    // Ambiguous in regex mode
    Expression expr = factory.createExpression("^Value (\\d+)$");
    // Throws: AmbiguousParameterTypeException
} catch (AmbiguousParameterTypeException e) {
    // Resolve by using preferForRegexpMatch
    ParameterType<Integer> preferred = new ParameterType<>(
        "count", "\\d+", Integer.class, Integer::parseInt, true, true
    );
}

// Not ambiguous in Cucumber Expression mode
Expression expr2 = factory.createExpression("Value {count}");  // Uses type1
Expression expr3 = factory.createExpression("Value {number}"); // Uses type2

Overlapping Patterns

// Specific pattern should be registered first
ParameterType<Email> emailType = new ParameterType<>(
    "email",
    "[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}",
    String.class,
    s -> s
);

ParameterType<String> wordType = new ParameterType<>(
    "word",
    "[\\w.+-]+",  // Overlaps with email pattern
    String.class,
    s -> s
);

// Register more specific type first
registry.defineParameterType(emailType);
registry.defineParameterType(wordType);

Performance Edge Cases

Very Long Expressions

// Expression with many parameters
StringBuilder exprBuilder = new StringBuilder("Data:");
for (int i = 0; i < 100; i++) {
    exprBuilder.append(" {int}");
}
Expression expr = factory.createExpression(exprBuilder.toString());
// Performance degrades with many parameters

Deeply Nested Optional Groups

// Deeply nested optionals
Expression expr = factory.createExpression(
    "I (have (seen (the (big (red (ball))))))"
);
// Complex pattern compilation and matching

Large Cache Size

// Cache with thousands of expressions
ConcurrentHashMap<String, Expression> cache = new ConcurrentHashMap<>();
for (int i = 0; i < 10000; i++) {
    String pattern = "Pattern " + i + " with {int}";
    Expression expr = factory.createExpression(pattern);
    cache.put(pattern, expr);
}
// Memory usage: ~10MB for 10,000 expressions

Next Steps

  • Quick Start Guide - Get started basics
  • Common Patterns - Frequently used patterns
  • Real-World Scenarios - Complete integration examples
  • Reference Documentation - Detailed API documentation