How can I introspect a freemarker template to find out what variables it uses?

Viewed 9729

I'm not at all sure that this is even a solvable problem, but supposing that I have a freemarker template, I'd like to be able to ask the template what variables it uses.

For my purposes, we can assume that the freemarker template is very simple - just "root level" entries (the model for such a template could be a simple Map). In other words, I don't need to handle templates that call for nested structures, etc.

7 Answers
public static Set<String> getNecessaryTemplateVariables(String templateName) throws TemplateModelException {
        Set<String> result = new HashSet<>();
        TemplateElement rootTreeNode = getTemplate(templateName).getRootTreeNode();
        if ("IteratorBlock".equals(rootTreeNode.getClass().getSimpleName())) {
            introspectFromIteratorBlock(result, rootTreeNode);
            return result;
        }
        for (int i = 0; i < rootTreeNode.getChildCount(); i++) {
            TemplateModel templateModel = rootTreeNode.getChildNodes().get(i);
            if (!(templateModel instanceof StringModel)) {
                continue;
            }
            Object wrappedObject = ((StringModel) templateModel).getWrappedObject();
            if ("DollarVariable".equals(wrappedObject.getClass().getSimpleName())) {
                introspectFromDollarVariable(result, wrappedObject);
            } else if ("ConditionalBlock".equals(wrappedObject.getClass().getSimpleName())) {
                introspectFromConditionalBlock(result, wrappedObject);
            } else if ("IfBlock".equals(wrappedObject.getClass().getSimpleName())) {
                introspectFromIfBlock(result, wrappedObject);
            } else if ("IteratorBlock".equals(wrappedObject.getClass().getSimpleName())) {
                introspectFromIteratorBlock(result, wrappedObject);
            }
        }
        return result;
    }

    private static void introspectFromIteratorBlock(Set<String> result, Object wrappedObject) {
        try {
            Object expression = getInternalState(wrappedObject, "listExp");
            result.add(getInternalState(expression, "name").toString());
        } catch (NoSuchFieldException | IllegalAccessException ignored) {
        }
    }

    private static void introspectFromConditionalBlock(Set<String> result, Object wrappedObject)
        throws TemplateModelException {
        try {
            Object expression = getInternalState(wrappedObject, "condition");
            if (expression == null) {
                return;
            }
            result.addAll(dealCommonExpression(expression));
            String nested = getInternalState(wrappedObject, "nestedBlock").toString();
            result.addAll(getNecessaryTemplateVariables(nested));
        } catch (NoSuchFieldException | IllegalAccessException ignored) {
        }
    }

    private static Set<String> dealCommonExpression(Object expression)
        throws NoSuchFieldException, IllegalAccessException {
        Set<String> ret = Sets.newHashSet();
        switch (expression.getClass().getSimpleName()) {
            case "ComparisonExpression":
                String reference = dealComparisonExpression(expression);
                ret.add(reference);
                break;
            case "ExistsExpression":
                reference = dealExistsExpression(expression);
                ret.add(reference);
                break;
            case "AndExpression":
                ret.addAll(dealAndExpression(expression));
            default:
                break;
        }
        return ret;
    }

    private static String dealComparisonExpression(Object expression)
        throws NoSuchFieldException, IllegalAccessException {
        Object left = getInternalState(expression, "left");
        Object right = getInternalState(expression, "right");
        String reference;
        if ("Identifier".equals(left.getClass().getSimpleName())) {
            reference = getInternalState(left, "name").toString();
        } else {
            reference = getInternalState(right, "name").toString();
        }
        return reference;
    }

    private static String dealExistsExpression(Object expression) throws NoSuchFieldException, IllegalAccessException {
        Object exp = getInternalState(expression, "exp");
        return getInternalState(exp, "name").toString();
    }

    private static Set<String> dealAndExpression(Object expression)  throws NoSuchFieldException,
        IllegalAccessException{
        Set<String> ret = Sets.newHashSet();
        Object lho = getInternalState(expression, "lho");
        ret.addAll(dealCommonExpression(lho));
        Object rho = getInternalState(expression, "rho");
        ret.addAll(dealCommonExpression(rho));
        return ret;
    }

    private static void introspectFromIfBlock(Set<String> result, Object wrappedObject)
        throws TemplateModelException {
        result.addAll(getNecessaryTemplateVariables(wrappedObject.toString()));
    }

    private static void introspectFromDollarVariable(Set<String> result, Object wrappedObject)
        throws TemplateModelException {
        try {
            Object expression = getInternalState(wrappedObject, "expression");
            switch (expression.getClass().getSimpleName()) {
                case "Identifier":
                    result.add(getInternalState(expression, "name").toString());
                    break;
                case "DefaultToExpression":
                    result.add(getInternalState(expression, "lho").toString());
                    break;
                case "BuiltinVariable":
                    break;
                default:
                    break;
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new TemplateModelException("Unable to reflect template model");
        }
    }

    private static Object getInternalState(Object o, String fieldName) throws NoSuchFieldException, IllegalAccessException {
        Field [] fieldArray = o.getClass().getDeclaredFields();
        for (Field field : fieldArray) {
            if (!field.getName().equals(fieldName)) {
                continue;
            }
            boolean wasAccessible = field.isAccessible();
            try {
                field.setAccessible(true);
                return field.get(o);
            } finally {
                field.setAccessible(wasAccessible);
            }
        }
        throw new NoSuchFieldException();
    }

    private static Template getTemplate(String templateName) {
        try {
            StringReader stringReader = new StringReader(templateName);
            return new Template(null, stringReader, null);
        } catch (IOException e) {
            throw new IllegalStateException("Failed to Load Template: " + templateName, e);
        }
    }

I optimized SimY4's answer, and supported <#list> and <#if> block. Code is not fully tested

Related