| package 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 org.apache.commons.lang.StringUtils; |
| import org.apache.velocity.context.InternalContextAdapter; |
| import org.apache.velocity.context.ProxyVMContext; |
| import org.apache.velocity.exception.MacroOverflowException; |
| import org.apache.velocity.exception.MethodInvocationException; |
| import org.apache.velocity.exception.TemplateInitException; |
| import org.apache.velocity.exception.VelocityException; |
| import org.apache.velocity.runtime.RuntimeConstants; |
| import org.apache.velocity.runtime.RuntimeServices; |
| import org.apache.velocity.runtime.parser.ParserTreeConstants; |
| import org.apache.velocity.runtime.parser.node.ASTDirective; |
| import org.apache.velocity.runtime.parser.node.Node; |
| import org.apache.velocity.runtime.parser.node.SimpleNode; |
| |
| /** |
| * VelocimacroProxy.java |
| * |
| * a proxy Directive-derived object to fit with the current directive system |
| * |
| * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> |
| * @version $Id$ |
| */ |
| public class VelocimacroProxy extends Directive |
| { |
| private String macroName; |
| private String[] argArray = null; |
| private String[] literalArgArray = null; |
| private SimpleNode nodeTree = null; |
| private int numMacroArgs = 0; |
| private boolean preInit = false; |
| private boolean strictArguments; |
| private boolean localContextScope = false; |
| private int maxCallDepth; |
| |
| /** |
| * Return name of this Velocimacro. |
| * @return The name of this Velocimacro. |
| */ |
| public String getName() |
| { |
| return macroName; |
| } |
| |
| /** |
| * Velocimacros are always LINE type directives. |
| * @return The type of this directive. |
| */ |
| public int getType() |
| { |
| return LINE; |
| } |
| |
| /** |
| * sets the directive name of this VM |
| * |
| * @param name |
| */ |
| public void setName(String name) |
| { |
| macroName = name; |
| } |
| |
| /** |
| * sets the array of arguments specified in the macro definition |
| * |
| * @param arr |
| */ |
| public void setArgArray(String[] arr) |
| { |
| argArray = arr; |
| |
| // for performance reasons we precache these strings - they are needed in |
| // "render literal if null" functionality |
| literalArgArray = new String[arr.length]; |
| for(int i = 0; i < arr.length; i++) |
| { |
| literalArgArray[i] = ".literal.$" + argArray[i]; |
| } |
| |
| /* |
| * get the arg count from the arg array. remember that the arg array has the macro name as |
| * it's 0th element |
| */ |
| |
| numMacroArgs = argArray.length - 1; |
| } |
| |
| /** |
| * @param tree |
| */ |
| public void setNodeTree(SimpleNode tree) |
| { |
| nodeTree = tree; |
| } |
| |
| /** |
| * returns the number of ars needed for this VM |
| * |
| * @return The number of ars needed for this VM |
| */ |
| public int getNumArgs() |
| { |
| return numMacroArgs; |
| } |
| |
| /** |
| * Renders the macro using the context. |
| * |
| * @param context Current rendering context |
| * @param writer Writer for output |
| * @param node AST that calls the macro |
| * @return True if the directive rendered successfully. |
| * @throws IOException |
| * @throws MethodInvocationException |
| * @throws MacroOverflowException |
| */ |
| public boolean render(InternalContextAdapter context, Writer writer, Node node) |
| throws IOException, MethodInvocationException, MacroOverflowException |
| { |
| // wrap the current context and add the macro arguments |
| |
| // the creation of this context is a major bottleneck (incl 2x HashMap) |
| final ProxyVMContext vmc = new ProxyVMContext(context, rsvc, localContextScope); |
| |
| int callArguments = node.jjtGetNumChildren(); |
| |
| if (callArguments > 0) |
| { |
| // the 0th element is the macro name |
| for (int i = 1; i < argArray.length && i <= callArguments; i++) |
| { |
| Node macroCallArgument = node.jjtGetChild(i - 1); |
| |
| /* |
| * literalArgArray[i] is needed for "render literal if null" functionality. |
| * The value is used in ASTReference render-method. |
| * |
| * The idea is to avoid generating the literal until absolutely necessary. |
| * |
| * This makes VMReferenceMungeVisitor obsolete and it would not work anyway |
| * when the macro AST is shared |
| */ |
| vmc.addVMProxyArg(context, argArray[i], literalArgArray[i], macroCallArgument); |
| } |
| } |
| |
| /* |
| * check that we aren't already at the max call depth |
| */ |
| if (maxCallDepth > 0 && maxCallDepth == vmc.getCurrentMacroCallDepth()) |
| { |
| String templateName = vmc.getCurrentTemplateName(); |
| Object[] stack = vmc.getMacroNameStack(); |
| |
| StringBuffer out = new StringBuffer(100) |
| .append("Max calling depth of ").append(maxCallDepth) |
| .append(" was exceeded in Template:").append(templateName) |
| .append(" and Macro:").append(macroName) |
| .append(" with Call Stack:"); |
| for (int i = 0; i < stack.length; i++) |
| { |
| if (i != 0) |
| { |
| out.append("->"); |
| } |
| out.append(stack[i]); |
| } |
| rsvc.getLog().error(out.toString()); |
| |
| try |
| { |
| throw new MacroOverflowException(out.toString()); |
| } |
| finally |
| { |
| // clean out the macro stack, since we just broke it |
| while (vmc.getCurrentMacroCallDepth() > 0) |
| { |
| vmc.popCurrentMacroName(); |
| } |
| } |
| } |
| |
| try |
| { |
| // render the velocity macro |
| vmc.pushCurrentMacroName(macroName); |
| nodeTree.render(vmc, writer); |
| vmc.popCurrentMacroName(); |
| return true; |
| } |
| catch (RuntimeException e) |
| { |
| throw e; |
| } |
| catch (Exception e) |
| { |
| String msg = "VelocimacroProxy.render() : exception VM = #" + macroName + "()"; |
| rsvc.getLog().error(msg, e); |
| throw new VelocityException(msg, e); |
| } |
| } |
| |
| /** |
| * Initialize members of VelocimacroProxy. called from MacroEntry |
| */ |
| public void init(RuntimeServices rs) |
| { |
| rsvc = rs; |
| |
| // this is a very expensive call (ExtendedProperties is very slow) |
| strictArguments = rs.getConfiguration().getBoolean( |
| RuntimeConstants.VM_ARGUMENTS_STRICT, false); |
| |
| // support for local context scope feature, where all references are local |
| // we do not have to check this at every invocation of ProxyVMContext |
| localContextScope = rsvc.getBoolean(RuntimeConstants.VM_CONTEXT_LOCALSCOPE, false); |
| |
| // get the macro call depth limit |
| maxCallDepth = rsvc.getInt(RuntimeConstants.VM_MAX_DEPTH); |
| } |
| |
| |
| /** |
| * Build an error message for not providing the correct number of arguments |
| */ |
| private String buildErrorMsg(Node node) |
| { |
| int i = node.jjtGetNumChildren(); // the number of arguments this call provided |
| String msg = "VM #" + macroName + ": too " |
| + ((getNumArgs() > i) ? "few" : "many") + " arguments to macro. Wanted " |
| + getNumArgs() + " got " + i; |
| return msg; |
| } |
| |
| /** |
| * check if we are calling this macro with the right number of arguments. If |
| * we are not, and strictArguments is active, then throw TemplateInitException. |
| * This method is called during macro render, so it must be thread safe. |
| */ |
| public void checkArgs(InternalContextAdapter context, Node node) |
| { |
| // check how many arguments we have |
| int i = node.jjtGetNumChildren(); |
| |
| // Throw exception for invalid number of arguments? |
| if (getNumArgs() != i) |
| { |
| if (strictArguments) |
| { |
| /** |
| * indicate col/line assuming it starts at 0 - this will be corrected one call up |
| */ |
| throw new TemplateInitException(buildErrorMsg(node), |
| context.getCurrentTemplateName(), 0, 0); |
| } |
| else if (rsvc.getLog().isDebugEnabled()) |
| { |
| rsvc.getLog().debug(buildErrorMsg(node)); |
| return; |
| } |
| } |
| } |
| } |
| |