public void addPattern(String pattern) {
        final List<String> tokens = splitByCharacter(pattern);

        PatternToken current = null;
        for (final PatternToken patternToken : roots) {
            if (patternToken.isMatch(tokens.get(0))) {
                current = patternToken;
                break;
            }
        }

        if (current == null) {
            current = new StringToken(tokens.get(0));
            roots.add(current);
        }

        if (tokens.size() == 1) {
            current.setExpression(pattern);
            return;
        }

        for (int i = 1; i < tokens.size(); i++) {
            final String token = tokens.get(i);
            PatternToken newToken;
            if (VarToken.VAR_TOKEN.equals(token)) {
                newToken = new VarToken();
            } else {
                newToken = new StringToken(token);
            }
            final PatternToken found = current.find(newToken);
            if (found == null) {
                current = current.add(newToken);
            } else {
                current = found;
            }
        }
        current.setExpression(pattern);
    }
@Test
    public void testTreeBuild() throws NoSuchFieldException, IllegalAccessException {
        PatternTree tree = new PatternTree();
        tree.addPattern("/products/{var}");
        tree.addPattern("/products/{var}/detail");
        tree.addPattern("/products/{var}/refund");
        tree.addPattern("/products/{var}/reorder/extra");
        tree.addPattern("/sales/{var}");
        tree.addPattern("/employees/{var}/profile");
        // This should map to exact same tree nodes
        tree.addPattern("produces/{var}/profile");
        tree.addPattern("GET:/posts/{var}");
        tree.addPattern("https://abc.com/posts/{var}");

        final Field rootField = PatternTree.class.getDeclaredField("roots");
        rootField.setAccessible(true);
        final List<PatternToken> roots = (List<PatternToken>) rootField.get(tree);

        final PatternToken prodToken = roots.get(0);
        Assertions.assertEquals(new StringToken("products"), prodToken);
        Assertions.assertEquals(1, prodToken.children().size());
        final PatternToken varToken = prodToken.children().get(0);
        Assertions.assertEquals(new VarToken(), varToken);
        Assertions.assertEquals(3, varToken.children().size());
        final PatternToken detailToken = varToken.children().get(0);
        Assertions.assertEquals(new StringToken("detail"), detailToken);

        final PatternToken salesToken = roots.get(1);
        Assertions.assertEquals(new StringToken("sales"), salesToken);
        Assertions.assertEquals(1, salesToken.children().size());

        final PatternToken employeeToken = roots.get(2);
        Assertions.assertEquals(new StringToken("employees"), employeeToken);
        Assertions.assertEquals(1, employeeToken.children().size());

        final PatternToken producesToken = roots.get(3);
        Assertions.assertEquals(new StringToken("produces"), producesToken);
        Assertions.assertEquals(1, producesToken.children().size());

        final PatternToken getPostsToken = roots.get(4);
        Assertions.assertEquals(new StringToken("GET:"), getPostsToken);

        final PatternToken abcToken = roots.get(5);
        Assertions.assertEquals(new StringToken("https:"), abcToken);
        final PatternToken abcComToken = abcToken.children().get(0);
        // For general performance purposes, double / will result in an empty string token
        // This is considered an intentional feature rather than a bug
        Assertions.assertEquals(new StringToken(""), abcComToken);
    }