void checkParamsSetAndApplyDefaults()

in freemarker-core/src/main/java/freemarker/core/Macro.java [255:400]


        void checkParamsSetAndApplyDefaults(Environment env) throws TemplateException {
            boolean resolvedADefaultValue, hasUnresolvedDefaultValue;
            Expression firstUnresolvedDefaultValueExpression;
            InvalidReferenceException firstInvalidReferenceExceptionForDefaultValue;

            final TemplateModel[] argsSpecVarDraft;
            if (Macro.this.requireArgsSpecialVariable) {
                argsSpecVarDraft = new TemplateModel[paramNames.length];
            } else {
                argsSpecVarDraft = null;
            }
            do { // Retried if there are unresolved defaults left
                firstUnresolvedDefaultValueExpression = null;
                firstInvalidReferenceExceptionForDefaultValue = null;
                resolvedADefaultValue = hasUnresolvedDefaultValue = false;
                for (int paramIndex = 0; paramIndex < paramNames.length; ++paramIndex) {
                    final String argName = paramNames[paramIndex];
                    final TemplateModel argValue = localVars.get(argName);
                    if (argValue == null) {
                        Expression defaultValueExp = paramNamesWithDefault.get(argName);
                        if (defaultValueExp != null) {
                            try {
                                TemplateModel defaultValue = defaultValueExp.eval(env);
                                if (defaultValue == null) {
                                    if (!hasUnresolvedDefaultValue) {
                                        firstUnresolvedDefaultValueExpression = defaultValueExp;
                                        hasUnresolvedDefaultValue = true;
                                    }
                                } else {
                                    localVars.put(argName, defaultValue);
                                    resolvedADefaultValue = true;

                                    if (argsSpecVarDraft != null) {
                                        argsSpecVarDraft[paramIndex] = defaultValue;
                                    }
                                }
                            } catch (InvalidReferenceException e) {
                                if (!hasUnresolvedDefaultValue) {
                                    hasUnresolvedDefaultValue = true;
                                    firstInvalidReferenceExceptionForDefaultValue = e;
                                }
                            }
                        } else if (!env.isClassicCompatible()) {
                            boolean argWasSpecified = localVars.containsKey(argName);
                            throw new _MiscTemplateException(env,
                                    new _ErrorDescriptionBuilder(
                                            "When calling ", (isFunction() ? "function" : "macro"), " ",
                                            new _DelayedJQuote(name), 
                                            ", required parameter ", new _DelayedJQuote(argName),
                                            " (parameter #", Integer.valueOf(paramIndex + 1), ") was ",
                                            (argWasSpecified
                                                    ? "specified, but had null/missing value."
                                                    : "not specified.") 
                                    ).tip(argWasSpecified
                                            ? new Object[] {
                                                    "If the parameter value expression on the caller side is known to "
                                                    + "be legally null/missing, you may want to specify a default "
                                                    + "value for it with the \"!\" operator, like "
                                                    + "paramValue!defaultValue." }
                                            : new Object[] { 
                                                    "If the omission was deliberate, you may consider making the "
                                                    + "parameter optional in the macro by specifying a default value "
                                                    + "for it, like ", "<#macro macroName paramName=defaultExpr>", ")" }
                                            ));
                        }
                    } else if (argsSpecVarDraft != null) {
                        // Minor performance problem here: If there are multiple iterations due to default value
                        // dependencies, this will set many parameters for multiple times.
                        argsSpecVarDraft[paramIndex] = argValue;
                    }
                }
            } while (hasUnresolvedDefaultValue && resolvedADefaultValue);
            if (hasUnresolvedDefaultValue) {
                if (firstInvalidReferenceExceptionForDefaultValue != null) {
                    throw firstInvalidReferenceExceptionForDefaultValue;
                } else if (!env.isClassicCompatible()) {
                    throw InvalidReferenceException.getInstance(firstUnresolvedDefaultValueExpression, env);
                }
            }
            
            if (argsSpecVarDraft != null) {
                final String catchAllParamName = getMacro().catchAllParamName;
                final TemplateModel catchAllArgValue = catchAllParamName != null
                        ? localVars.get(catchAllParamName) : null;

                if (getMacro().isFunction()) {
                    int lengthWithCatchAlls = argsSpecVarDraft.length;
                    if (catchAllArgValue != null) {
                        lengthWithCatchAlls += ((TemplateSequenceModel) catchAllArgValue).size();
                    }

                    SimpleSequence argsSpecVarValue = new SimpleSequence(
                            lengthWithCatchAlls, _ObjectWrappers.SAFE_OBJECT_WRAPPER);
                    for (int paramIndex = 0; paramIndex < argsSpecVarDraft.length; paramIndex++) {
                        argsSpecVarValue.add(argsSpecVarDraft[paramIndex]);
                    }
                    if (catchAllParamName != null) {
                        TemplateSequenceModel catchAllSeq = (TemplateSequenceModel) catchAllArgValue;
                        int catchAllSize = catchAllSeq.size();
                        for (int j = 0; j < catchAllSize; j++) {
                            argsSpecVarValue.add(catchAllSeq.get(j));
                        }
                    }
                    assert argsSpecVarValue.size() == lengthWithCatchAlls;

                    this.argsSpecialVariableValue = argsSpecVarValue;
                } else { // #macro
                    int lengthWithCatchAlls = argsSpecVarDraft.length;
                    TemplateHashModelEx2 catchAllHash;
                    if (catchAllParamName != null) {
                        if (catchAllArgValue instanceof TemplateSequenceModel) {
                            if (((TemplateSequenceModel) catchAllArgValue).size() != 0) {
                                throw new _MiscTemplateException("The macro can only by called with named arguments, " +
                                        "because it uses both .", BuiltinVariable.ARGS, " and a non-empty catch-all " +
                                        "parameter.");
                            }
                            catchAllHash = Constants.EMPTY_HASH_EX2;
                        } else {
                            catchAllHash = (TemplateHashModelEx2) catchAllArgValue;
                        }
                        lengthWithCatchAlls += catchAllHash.size();
                    } else {
                        catchAllHash = null;
                    }

                    SimpleHash argsSpecVarValue = new SimpleHash(
                            new LinkedHashMap<String, Object>(lengthWithCatchAlls * 4 / 3, 1.0f),
                            _ObjectWrappers.SAFE_OBJECT_WRAPPER, 0);
                    for (int paramIndex = 0; paramIndex < argsSpecVarDraft.length; paramIndex++) {
                        argsSpecVarValue.put(paramNames[paramIndex], argsSpecVarDraft[paramIndex]);
                    }
                    if (catchAllArgValue != null) {
                        for (TemplateHashModelEx2.KeyValuePairIterator iter = catchAllHash.keyValuePairIterator();
                                iter.hasNext(); ) {
                            TemplateHashModelEx2.KeyValuePair kvp = iter.next();
                            argsSpecVarValue.put(
                                    ((TemplateScalarModel) kvp.getKey()).getAsString(),
                                    kvp.getValue());
                        }
                    }
                    assert argsSpecVarValue.size() == lengthWithCatchAlls;

                    this.argsSpecialVariableValue = argsSpecVarValue;
                }
            } // if (argsSpecVarDraft != null)
        }