protected Maybe doCall()

in core/src/main/java/org/apache/brooklyn/core/workflow/ShorthandProcessor.java [130:300]


        protected Maybe<Object> doCall() {
            boolean isEndOfOptional = false;
            outer: while (true) {
                if (isEndOfOptional) {
                    if (optionalDepth <= 0) {
                        throw new IllegalStateException("Unexpected optional block closure");
                    }
                    optionalDepth--;
                    if (optionalSkippingInput>0) {
                        // we were in a block where we skipped something optional because it couldn't be matched; outer parser is now canonical,
                        // and should stop skipping
                        return Maybe.of(true);
                    }
                    isEndOfOptional = false;
                }

                if (templateTokens.isEmpty()) {
                    if (Strings.isNonBlank(inputRemaining) && valueUpdater==null) {
                        return Maybe.absent("Input has trailing characters after template is matched: '" + inputRemaining + "'");
                    }
                    if (optionalDepth>0)
                        return Maybe.absent("Mismatched optional marker in template");
                    return Maybe.of(true);
                }
                String t = templateTokens.remove(0);

                if (t.startsWith("[")) {
                    t = t.substring(1);
                    if (!t.isEmpty()) {
                        templateTokens.add(0, t);
                    }
                    String optionalPresentVar = null;
                    if (!templateTokens.isEmpty() && templateTokens.get(0).startsWith("?")) {
                        String v = templateTokens.remove(0);
                        if (v.startsWith("?${") && v.endsWith("}")) {
                            optionalPresentVar = v.substring(3, v.length() - 1);
                        } else {
                            throw new IllegalStateException("? after [ should indicate optional presence variable using syntax '?${var}', not '"+v+"'");
                        }
                    }
                    Maybe<Object> cr;
                    if (optionalSkippingInput<=0) {
                        // make a deep copy so that valueUpdater writes get replayed
                        Map<String, Object> backupResult = (Map) CollectionMerger.builder().deep(true).build().merge(MutableMap.of(), result);
                        Consumer<String> backupValueUpdater = valueUpdater;
                        String backupInputRemaining = inputRemaining;
                        List<String> backupTemplateTokens = MutableList.copyOf(templateTokens);
                        int oldDepth = optionalDepth;
                        int oldSkippingDepth = optionalSkippingInput;

                        optionalDepth++;
                        cr = doCall();
                        if (cr.isPresent()) {
                            // succeeded
                            if (optionalPresentVar!=null) result.put(optionalPresentVar, true);
                            continue;

                        } else {
                            // restore
                            result = backupResult;
                            valueUpdater = backupValueUpdater;
                            if (optionalPresentVar!=null) result.put(optionalPresentVar, false);
                            inputRemaining = backupInputRemaining;
                            templateTokens.clear();
                            templateTokens.addAll(backupTemplateTokens);
                            optionalDepth = oldDepth;
                            optionalSkippingInput = oldSkippingDepth;

                            optionalSkippingInput++;
                            optionalDepth++;
                            cr = doCall();
                            if (cr.isPresent()) {
                                optionalSkippingInput--;
                                continue;
                            }
                        }
                    } else {
                        if (optionalPresentVar!=null) {
                            result.put(optionalPresentVar, false);
                            valueUpdater = null;
                        }
                        optionalDepth++;
                        cr = doCall();
                        if (cr.isPresent()) {
                            continue;
                        }
                    }
                    return cr;
                }

                isEndOfOptional = t.endsWith("]");

                if (isEndOfOptional) {
                    t = t.substring(0, t.length() - 1);
                    if (t.isEmpty()) continue;
                    // next loop will process the end of the optionality
                }

                if (qst.isQuoted(t)) {
                    if (optionalSkippingInput>0) continue;

                    String literal = qst.unwrapIfQuoted(t);
                    do {
                        // ignore leading spaces (since the quoted string tokenizer will have done that anyway); but their _absence_ can be significant for intra-token searching when matching a var
                        inputRemaining = Strings.trimStart(inputRemaining);
                        if (inputRemaining.startsWith(Strings.trimStart(literal))) {
                            // literal found
                            inputRemaining = inputRemaining.substring(Strings.trimStart(literal).length());
                            continue outer;
                        }
                        if (inputRemaining.isEmpty()) return Maybe.absent("Literal '"+literal+"' expected, when end of input reached");
                        if (valueUpdater!=null) {
                            QuotedStringTokenizer qstInput = qst(inputRemaining);
                            if (!qstInput.hasMoreTokens()) return Maybe.absent("Literal '"+literal+"' expected, when end of input tokens reached");
                            String value = getNextInputTokenUpToPossibleExpectedLiteral(qstInput, literal);
                            valueUpdater.accept(value);
                            continue;
                        }
                        return Maybe.absent("Literal '"+literal+"' expected, when encountered '"+inputRemaining+"'");
                    } while (true);
                }

                if (t.startsWith("${") && t.endsWith("}")) {
                    if (optionalSkippingInput>0) continue;

                    t = t.substring(2, t.length()-1);
                    String value;

                    inputRemaining = inputRemaining.trim();
                    QuotedStringTokenizer qstInput = qst(inputRemaining);
                    if (!qstInput.hasMoreTokens()) return Maybe.absent("End of input when looking for variable "+t);

                    if (!templateTokens.stream().filter(x -> !x.equals("]")).findFirst().isPresent()) {
                        // last word (whether optional or not) takes everything
                        value = getRemainderPossiblyRaw(qstInput);
                        inputRemaining = "";

                    } else {
                        value = getNextInputTokenUpToPossibleExpectedLiteral(qstInput, null);
                    }
                    boolean multiMatch = t.endsWith("...");
                    if (multiMatch) t = Strings.removeFromEnd(t, "...");
                    String keys[] = t.split("\\.");
                    final String tt = t;
                    valueUpdater = v2 -> {
                        Map target = result;
                        for (int i=0; i<keys.length; i++) {
                            if (!Pattern.compile("[A-Za-z0-9_-]+").matcher(keys[i]).matches()) {
                                throw new IllegalArgumentException("Invalid variable '"+tt+"'");
                            }
                            if (i == keys.length - 1) {
                                target.compute(keys[i], (k, v) -> v == null ? v2 : v + " " + v2);
                            } else {
                                // need to make sure we have a map or null
                                target = (Map) target.compute(keys[i], (k, v) -> {
                                    if (v == null) return MutableMap.of();
                                    if (v instanceof Map) return v;
                                    return Maybe.absent("Cannot process shorthand for " + Arrays.asList(keys) + " because entry '" + k + "' is not a map (" + v + ")");
                                });
                            }
                        }
                    };
                    valueUpdater.accept(value);
                    if (!multiMatch) valueUpdater = null;
                    continue;
                }

                // unexpected token
                return Maybe.absent("Unexpected token in shorthand pattern '"+template+"' at position "+(template.lastIndexOf(t)+1));
            }
        }