bboss.org.apache.velocity.runtime.directive.RuntimeMacro.java Source code

Java tutorial

Introduction

Here is the source code for bboss.org.apache.velocity.runtime.directive.RuntimeMacro.java

Source

package bboss.org.apache.velocity.runtime.directive;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.io.IOException;
import java.io.Writer;
import java.util.List;

import org.apache.commons.lang.text.StrBuilder;
import bboss.org.apache.velocity.context.InternalContextAdapter;
import bboss.org.apache.velocity.exception.MethodInvocationException;
import bboss.org.apache.velocity.exception.ParseErrorException;
import bboss.org.apache.velocity.exception.ResourceNotFoundException;
import bboss.org.apache.velocity.exception.TemplateInitException;
import bboss.org.apache.velocity.exception.VelocityException;
import bboss.org.apache.velocity.runtime.Renderable;
import bboss.org.apache.velocity.runtime.RuntimeConstants;
import bboss.org.apache.velocity.runtime.RuntimeServices;
import bboss.org.apache.velocity.runtime.log.Log;
import bboss.org.apache.velocity.runtime.parser.ParserTreeConstants;
import bboss.org.apache.velocity.runtime.parser.Token;
import bboss.org.apache.velocity.runtime.parser.node.Node;
import bboss.org.apache.velocity.util.introspection.Info;

/**
 * This class acts as a proxy for potential macros.  When the AST is built
 * this class is inserted as a placeholder for the macro (whether or not
 * the macro is actually defined).  At render time we check whether there is
 * a implementation for the macro call. If an implementation cannot be
 * found the literal text is rendered.
 * @since 1.6
 */
public class RuntimeMacro extends Directive {
    /**
     * Name of the macro
     */
    private String macroName;

    /**
     * Literal text of the macro
     */
    private String literal = null;

    /**
     * Node of the macro call
     */
    private Node node = null;

    /**
     * Indicates if we are running in strict reference mode.
     */
    protected boolean strictRef = false;

    /**
     * badArgsErrorMsg will be non null if the arguments to this macro
     * are deamed bad at init time, see the init method.  If his is non null, then this macro 
     * cannot be rendered, and if there is an attempt to render we throw an exception
     * with this as the message.
     */
    private String badArgsErrorMsg = null;

    /**
     * Create a RuntimeMacro instance. Macro name and source
     * template stored for later use.
     *
     * @param macroName name of the macro
     */
    public RuntimeMacro(String macroName) {
        if (macroName == null) {
            throw new IllegalArgumentException("Null arguments");
        }

        this.macroName = macroName.intern();
    }

    /**
     * Return name of this Velocimacro.
     *
     * @return The name of this Velocimacro.
     */
    public String getName() {
        return macroName;
    }

    /**
     * Override to always return "macro".  We don't want to use
     * the macro name here, since when writing VTL that uses the
     * scope, we are within a #macro call.  The macro name will instead
     * be used as the scope name when defining the body of a BlockMacro.
     */
    public String getScopeName() {
        return "macro";
    }

    /**
     * Velocimacros are always LINE
     * type directives.
     *
     * @return The type of this directive.
     */
    public int getType() {
        return LINE;
    }

    /**
     * Intialize the Runtime macro. At the init time no implementation so we
     * just save the values to use at the render time.
     *
     * @param rs runtime services
     * @param context InternalContextAdapter
     * @param node node containing the macro call
     */
    public void init(RuntimeServices rs, InternalContextAdapter context, Node node) {
        super.init(rs, context, node);
        rsvc = rs;
        this.node = node;

        /**
         * Apply strictRef setting only if this really looks like a macro,
         * so strict mode doesn't balk at things like #E0E0E0 in a template.
         * compare with ")" is a simple #foo() style macro, comparing to
         * "#end" is a block style macro. We use starts with because the token
         * may end with '\n'
         */
        Token t = node.getLastToken();
        if (t.image.startsWith(")") || t.image.startsWith("#end")) {
            strictRef = rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false);
        }

        // Validate that none of the arguments are plain words, (VELOCITY-614)
        // they should be string literals, references, inline maps, or inline lists
        for (int n = 0; n < node.jjtGetNumChildren(); n++) {
            Node child = node.jjtGetChild(n);
            if (child.getType() == ParserTreeConstants.JJTWORD) {
                badArgsErrorMsg = "Invalid arg '" + child.getFirstToken().image + "' in macro #" + macroName
                        + " at " + Log.formatFileString(child);

                if (strictRef) // If strict, throw now
                {
                    /* indicate col/line assuming it starts at 0
                     * this will be corrected one call up  */
                    throw new TemplateInitException(badArgsErrorMsg, context.getCurrentTemplateName(), 0, 0);
                }
            }
        }
    }

    /**
     * It is probably quite rare that we need to render the macro literal
     * so do it only on-demand and then cache the value. This tactic helps to
     * reduce memory usage a bit.
     */
    private String getLiteral() {
        if (literal == null) {
            StrBuilder buffer = new StrBuilder();
            Token t = node.getFirstToken();

            while (t != null && t != node.getLastToken()) {
                buffer.append(t.image);
                t = t.next;
            }

            if (t != null) {
                buffer.append(t.image);
            }

            literal = buffer.toString();
        }
        return literal;
    }

    /**
     * Velocimacro implementation is not known at the init time. So look for
     * a implementation in the macro libaries and if finds one renders it. The
     * actual rendering is delegated to the VelocimacroProxy object. When
     * looking for a macro we first loot at the template with has the
     * macro call then we look at the macro lbraries in the order they appear
     * in the list. If a macro has many definitions above look up will
     * determine the precedence.
     *
     * @param context
     * @param writer
     * @param node
     * @return true if the rendering is successful
     * @throws IOException
     * @throws ResourceNotFoundException
     * @throws ParseErrorException
     * @throws MethodInvocationException
     */
    public boolean render(InternalContextAdapter context, Writer writer, Node node)
            throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
        return render(context, writer, node, null);
    }

    /**
     * This method is used with BlockMacro when we want to render a macro with a body AST.
     *
     * @param context
     * @param writer
     * @param node
     * @param body AST block that was enclosed in the macro body.
     * @return true if the rendering is successful
     * @throws IOException
     * @throws ResourceNotFoundException
     * @throws ParseErrorException
     * @throws MethodInvocationException
     */
    public boolean render(InternalContextAdapter context, Writer writer, Node node, Renderable body)
            throws IOException, ResourceNotFoundException, ParseErrorException, MethodInvocationException {
        VelocimacroProxy vmProxy = null;
        String renderingTemplate = context.getCurrentTemplateName();

        /**
         * first look in the source template
         */
        Object o = rsvc.getVelocimacro(macroName, getTemplateName(), renderingTemplate);

        if (o != null) {
            // getVelocimacro can only return a VelocimacroProxy so we don't need the
            // costly instanceof check
            vmProxy = (VelocimacroProxy) o;
        }

        /**
         * if not found, look in the macro libraries.
         */
        if (vmProxy == null) {
            List macroLibraries = context.getMacroLibraries();
            if (macroLibraries != null) {
                for (int i = macroLibraries.size() - 1; i >= 0; i--) {
                    o = rsvc.getVelocimacro(macroName, (String) macroLibraries.get(i), renderingTemplate);

                    // get the first matching macro
                    if (o != null) {
                        vmProxy = (VelocimacroProxy) o;
                        break;
                    }
                }
            }
        }

        if (vmProxy != null) {
            try {
                // mainly check the number of arguments
                vmProxy.checkArgs(context, node, body != null);
            } catch (TemplateInitException die) {
                throw new ParseErrorException(die.getMessage() + " at " + Log.formatFileString(node),
                        new Info(node));
            }

            if (badArgsErrorMsg != null) {
                throw new TemplateInitException(badArgsErrorMsg, context.getCurrentTemplateName(), node.getColumn(),
                        node.getLine());
            }

            try {
                preRender(context);
                return vmProxy.render(context, writer, node, body);
            } catch (StopCommand stop) {
                if (!stop.isFor(this)) {
                    throw stop;
                }
                return true;
            } catch (RuntimeException e) {
                /**
                 * We catch, the exception here so that we can record in
                 * the logs the template and line number of the macro call
                 * which generate the exception.  This information is
                 * especially important for multiple macro call levels.
                 * this is also true for the following catch blocks.
                 */
                rsvc.getLog()
                        .error("Exception in macro #" + macroName + " called at " + Log.formatFileString(node));
                throw e;
            } catch (IOException e) {
                rsvc.getLog()
                        .error("Exception in macro #" + macroName + " called at " + Log.formatFileString(node));
                throw e;
            } finally {
                postRender(context);
            }
        } else if (strictRef) {
            throw new VelocityException(
                    "Macro '#" + macroName + "' is not defined at " + Log.formatFileString(node));
        }

        /**
         * If we cannot find an implementation write the literal text
         */
        writer.write(getLiteral());
        return true;
    }
}