com.github.jknack.handlebars.internal.Variable.java Source code

Java tutorial

Introduction

Here is the source code for com.github.jknack.handlebars.internal.Variable.java

Source

/**
 * Copyright (c) 2012-2015 Edgar Espina
 *
 * This file is part of Handlebars.java.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.github.jknack.handlebars.internal;

import java.io.IOException;
import java.io.Writer;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.EscapingStrategy;
import com.github.jknack.handlebars.Formatter;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.HelperRegistry;
import com.github.jknack.handlebars.Lambda;
import com.github.jknack.handlebars.Options;
import com.github.jknack.handlebars.PathCompiler;
import com.github.jknack.handlebars.PathExpression;
import com.github.jknack.handlebars.TagType;
import com.github.jknack.handlebars.Template;

/**
 * The most basic tag type is the variable. A {{name}} tag in a basic template
 * will try to find the name key in the current context. If there is no name
 * key, nothing will be rendered.
 * All variables are HTML escaped by default. If you want to return unescaped
 * HTML, use the triple mustache: {{{name}}}.
 * You can also use & to unescape a variable: {{& name}}. This may be useful
 * when changing delimiters.
 *
 * @author edgar.espina
 * @since 0.1.0
 */
class Variable extends HelperResolver {

    /**
     * The variable's name. Required.
     */
    protected final String name;

    /**
     * The variable type.
     */
    protected final TagType type;

    /**
     * The start delimiter.
     */
    private String startDelimiter;

    /**
     * The end delimiter.
     */
    private String endDelimiter;

    /**
     * The escaping strategy.
     */
    private EscapingStrategy escapingStrategy;

    /** Helper. */
    private Helper<Object> helper;

    /** Formatter. */
    private Formatter.Chain formatter;

    /** Missing value resolver. */
    private Helper<Object> missing;

    /** A compiled version of {@link #name}. */
    private List<PathExpression> path;

    /** True, when no param/hash. */
    private boolean noArg;

    /**
     * Creates a new {@link Variable}.
     *
     * @param handlebars The handlebars instance.
     * @param name The variable's name. Required.
     * @param type The variable's type. Required.
     * @param params The variable's parameters. Required.
     * @param hash The variable's hash. Required.
     */
    public Variable(final Handlebars handlebars, final String name, final TagType type, final List<Object> params,
            final Map<String, Object> hash) {
        super(handlebars);
        this.name = name.trim();
        this.path = PathCompiler.compile(name);
        this.type = type;
        params(params);
        hash(hash);
        this.escapingStrategy = handlebars.getEscapingStrategy();
        this.formatter = handlebars.getFormatter();
        this.noArg = params.size() == 0 && hash.size() == 0;
        postInit();
    }

    /**
     * Creates a new {@link Variable}.
     *
     * @param handlebars The handlebars instance.
     * @param name The variable's name. Required.
     * @param type The variable's type. Required.
     */
    @SuppressWarnings("unchecked")
    public Variable(final Handlebars handlebars, final String name, final TagType type) {
        this(handlebars, name, type, Collections.EMPTY_LIST, Collections.EMPTY_MAP);
    }

    /**
     * Apply any pending initialization.
     */
    protected void postInit() {
        this.helper = helper(name);
        this.missing = handlebars.helper(HelperRegistry.HELPER_MISSING);
    }

    /**
     * The variable's name.
     *
     * @return The variable's name.
     */
    public String name() {
        return name;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void merge(final Context scope, final Writer writer) throws IOException {
        Helper<Object> helper = this.helper;
        boolean blockParam = scope.isBlockParams() && noArg;
        if (helper != null && !blockParam) {
            Options options = new Options.Builder(handlebars, name, type, scope, empty(this))
                    .setParams(params(scope)).setHash(hash(scope)).setWriter(writer).build();
            options.data(Context.PARAM_SIZE, this.params.size());
            CharSequence result = helper.apply(determineContext(scope), options);
            writer.append(formatAndEscape(result, Formatter.NOOP));
        } else {
            Object value = scope.get(path);
            if (value == null) {
                if (missing != null) {
                    Options options = new Options.Builder(handlebars, name, type, scope, empty(this))
                            .setParams(params(scope)).setHash(hash(scope)).setWriter(writer).build();
                    value = missing.apply(determineContext(scope), options);
                }
            }
            if (value instanceof Lambda) {
                value = Lambdas.merge(handlebars, (Lambda<Object, Object>) value, scope, this);
            }
            writer.append(formatAndEscape(value, formatter));
        }
    }

    /**
     * @param variable Source template.
     * @return An empty template.
     */
    private static Template empty(final Variable variable) {
        return new ForwardingTemplate(variable) {
            @Override
            public String apply(final Context context) throws IOException {
                return "";
            }

            @Override
            public void apply(final Context context, final Writer writer) throws IOException {
            }

            @Override
            public String apply(final Object context) throws IOException {
                return "";
            }

            @Override
            public void apply(final Object context, final Writer writer) throws IOException {
            }
        };
    }

    @Override
    protected void collect(final Collection<String> result, final TagType tagType) {
        if (this.type == tagType) {
            result.add(name);
        }
        super.collect(result, tagType);
    }

    @Override
    protected void collectReferenceParameters(final Collection<String> result) {
        for (Object param : params) {
            if (ParamType.REFERENCE.apply(param) && !ParamType.STRING.apply(param)) {
                result.add((String) param);
            }
        }
        for (Object hashValue : hash.values()) {
            if (ParamType.REFERENCE.apply(hashValue) && !ParamType.STRING.apply(hashValue)) {
                result.add((String) hashValue);
            }
        }
        super.collectReferenceParameters(result);
    }

    /**
     * True if the given value should be escaped.
     *
     * @param value The variable's value.
     * @param formatter Formatter to use.
     * @return True if the given value should be escaped.
     */
    private CharSequence formatAndEscape(final Object value, final Formatter.Chain formatter) {
        if (value == null) {
            return StringUtils.EMPTY;
        }
        CharSequence formatted = formatter.format(value).toString();
        if (value instanceof Handlebars.SafeString) {
            return formatted;
        }
        if (type == TagType.VAR) {
            return escapingStrategy.escape(formatted);
        }
        return formatted;
    }

    @Override
    public String text() {
        StringBuilder buffer = new StringBuilder();
        buffer.append(startDelimiter).append(suffix()).append(name);
        String params = paramsToString(this.params);
        if (params.length() > 0) {
            buffer.append(" ").append(params);
        }
        String hash = hashToString();
        if (hash.length() > 0) {
            buffer.append(" ").append(hash);
        }
        return buffer.append(endDelimiter).toString();
    }

    /**
     * @return Type suffix, default is empty.
     */
    protected String suffix() {
        return "";
    }

    /**
     * Set the end delimiter.
     *
     * @param endDelimiter The end delimiter.
     * @return This section.
     */
    public Variable endDelimiter(final String endDelimiter) {
        this.endDelimiter = endDelimiter;
        return this;
    }

    /**
     * Set the start delimiter.
     *
     * @param startDelimiter The start delimiter.
     * @return This section.
     */
    public Variable startDelimiter(final String startDelimiter) {
        this.startDelimiter = startDelimiter;
        return this;
    }

    /**
     * The start delimiter.
     *
     * @return The start delimiter.
     */
    public String startDelimiter() {
        return startDelimiter;
    }

    /**
     * The end delimiter.
     *
     * @return The end delimiter.
     */
    public String endDelimiter() {
        return endDelimiter;
    }

}