1 package org.apache.velocity.runtime.directive; 2 3 /* 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 */ 21 22 import org.apache.velocity.context.InternalContextAdapter; 23 import org.apache.velocity.exception.TemplateInitException; 24 import org.apache.velocity.runtime.RuntimeServices; 25 import org.apache.velocity.runtime.parser.ParseException; 26 import org.apache.velocity.runtime.parser.Token; 27 import org.apache.velocity.runtime.parser.node.Node; 28 import org.apache.velocity.runtime.parser.node.ParserTreeConstants; 29 30 import java.io.IOException; 31 import java.io.Writer; 32 import java.util.ArrayList; 33 import java.util.List; 34 35 /** 36 * Macro implements the macro definition directive of VTL. 37 * 38 * example: 39 * 40 * #macro( isnull $i ) 41 * #if( $i ) 42 * $i 43 * #end 44 * #end 45 * 46 * This object is used at parse time to mainly process and register the 47 * macro. It is used inline in the parser when processing a directive. 48 * 49 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> 50 * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a> 51 * @version $Id$ 52 */ 53 public class Macro extends Directive 54 { 55 private static boolean debugMode = false; 56 57 /** 58 * Return name of this directive. 59 * @return The name of this directive. 60 */ 61 @Override getName()62 public String getName() 63 { 64 return "macro"; 65 } 66 67 /** 68 * Return type of this directive. 69 * @return The type of this directive. 70 */ 71 @Override getType()72 public int getType() 73 { 74 return BLOCK; 75 } 76 77 /** 78 * Since this class does no processing of content, 79 * there is never a need for an internal scope. 80 */ 81 @Override isScopeProvided()82 public boolean isScopeProvided() 83 { 84 return false; 85 } 86 87 /** 88 * render() doesn't do anything in the final output rendering. 89 * There is no output from a #macro() directive. 90 * @param context 91 * @param writer 92 * @param node 93 * @return True if the directive rendered successfully. 94 * @throws IOException 95 */ 96 @Override render(InternalContextAdapter context, Writer writer, Node node)97 public boolean render(InternalContextAdapter context, 98 Writer writer, Node node) 99 throws IOException 100 { 101 /* 102 * do nothing: We never render. The VelocimacroProxy object does that 103 */ 104 105 return true; 106 } 107 108 /** 109 * @see org.apache.velocity.runtime.directive.Directive#init(org.apache.velocity.runtime.RuntimeServices, org.apache.velocity.context.InternalContextAdapter, org.apache.velocity.runtime.parser.node.Node) 110 */ 111 @Override init(RuntimeServices rs, InternalContextAdapter context, Node node)112 public void init(RuntimeServices rs, InternalContextAdapter context, 113 Node node) 114 throws TemplateInitException 115 { 116 super.init(rs, context, node); 117 118 119 // Add this macro to the VelocimacroManager now that it has been initialized. 120 List<MacroArg> macroArgs = getArgArray(node, rsvc); 121 int numArgs = node.jjtGetNumChildren(); 122 rsvc.addVelocimacro(macroArgs.get(0).name, node.jjtGetChild(numArgs - 1), 123 macroArgs, node.getTemplate()); 124 } 125 126 /** 127 * Check the argument types of a macro call, called by the parser to do validation 128 */ 129 @Override checkArgs(ArrayList<Integer> argtypes, Token t, String templateName)130 public void checkArgs(ArrayList<Integer> argtypes, Token t, String templateName) 131 throws ParseException 132 { 133 if (argtypes.size() < 1) 134 { 135 throw new MacroParseException("A macro definition requires at least a macro name" 136 , templateName, t); 137 } 138 139 /* 140 * lets make sure that the first arg is an ASTWord 141 */ 142 if(argtypes.get(0) != ParserTreeConstants.JJTWORD) 143 { 144 throw new MacroParseException("Macro argument 1" 145 + " must be a token without surrounding \' or \"" 146 , templateName, t); 147 } 148 149 150 // We use this to flag if the default arguments are out of order. such as 151 // #macro($a $b=1 $c). We enforce that all default parameters must be 152 // specified consecutively, and at the end of the argument list. 153 boolean consecutive = false; 154 155 // All arguments other then the first must be either a reference 156 // or a directiveassign followed by a reference in the case a default 157 // value is specified. 158 for (int argPos = 1; argPos < argtypes.size(); argPos++) 159 { 160 if (argtypes.get(argPos) == ParserTreeConstants.JJTDIRECTIVEASSIGN) 161 { 162 // Absorb next argument type since parser enforces that these are in 163 // pairs, and we don't need to check the type of the second 164 // arg because it is done by the parser. 165 argPos++; 166 consecutive = true; 167 } 168 else if (argtypes.get(argPos) != ParserTreeConstants.JJTREFERENCE) 169 { 170 throw new MacroParseException("Macro argument " + (argPos + 1) 171 + " must be a reference", templateName, t); 172 } 173 else if (consecutive) 174 { 175 // We have already found a default parameter e.g.; $x = 2, but 176 // the next parameter was not a reference. 177 throw new MacroParseException("Macro non-default argument follows a default argument at " 178 , templateName, t); 179 } 180 } 181 } 182 183 /** 184 * Creates an array containing the literal text from the macro 185 * argument(s) (including the macro's name as the first arg). 186 * 187 * @param node The parse node from which to grok the argument 188 * list. It's expected to include the block node tree (for the 189 * macro body). 190 * @param rsvc For debugging purposes only. 191 * @return array of arguments 192 */ getArgArray(Node node, RuntimeServices rsvc)193 private static List<MacroArg> getArgArray(Node node, RuntimeServices rsvc) 194 { 195 /* 196 * Get the number of arguments for the macro, excluding the 197 * last child node which is the block tree containing the 198 * macro body. 199 */ 200 int numArgs = node.jjtGetNumChildren(); 201 numArgs--; // avoid the block tree... 202 203 ArrayList<MacroArg> macroArgs = new ArrayList<>(); 204 205 for (int i = 0; i < numArgs; i++) 206 { 207 Node curnode = node.jjtGetChild(i); 208 MacroArg macroArg = new MacroArg(); 209 if (curnode.getType() == ParserTreeConstants.JJTDIRECTIVEASSIGN) 210 { 211 // This is an argument with a default value 212 macroArg.name = curnode.getFirstTokenImage(); 213 214 // Inforced by the parser there will be an argument here. 215 i++; 216 curnode = node.jjtGetChild(i); 217 macroArg.defaultVal = curnode; 218 } 219 else 220 { 221 // An argument without a default value 222 macroArg.name = curnode.getFirstTokenImage(); 223 } 224 225 // trim off the leading $ for the args after the macro name. 226 // saves everyone else from having to do it 227 if (i > 0 && macroArg.name.startsWith(String.valueOf(rsvc.getParserConfiguration().getDollarChar()))) 228 { 229 macroArg.name = macroArg.name.substring(1); 230 } 231 232 macroArgs.add(macroArg); 233 } 234 235 if (debugMode) 236 { 237 StringBuilder msg = new StringBuilder("Macro.getArgArray(): nbrArgs="); 238 msg.append(numArgs).append(": "); 239 macroToString(msg, macroArgs, rsvc); 240 rsvc.getLog("macro").debug(msg.toString()); 241 } 242 243 return macroArgs; 244 } 245 246 /** 247 * MacroArgs holds the information for a single argument in a 248 * macro definition. The arguments for a macro are passed around as a 249 * list of these objects. 250 */ 251 public static class MacroArg 252 { 253 /** 254 * Name of the argument with '$' stripped off 255 */ 256 public String name = null; 257 258 /** 259 * If the argument was given a default value, then this contains 260 * the base of the AST tree of the value. Otherwise it is null. 261 */ 262 public Node defaultVal = null; 263 } 264 265 /** 266 * For debugging purposes. Formats the arguments from 267 * <code>argArray</code> and appends them to <code>buf</code>. 268 * 269 * @param buf A StringBuilder. If null, a new StringBuilder is allocated. 270 * @param macroArgs Array of macro arguments, containing the 271 * #macro() arguments and default values. the 0th is the name. 272 * @return A StringBuilder containing the formatted arguments. If a StringBuilder 273 * has passed in as buf, this method returns it. 274 * @since 1.5 275 */ macroToString(final StringBuilder buf, List<MacroArg> macroArgs, RuntimeServices rsvc)276 public static StringBuilder macroToString(final StringBuilder buf, List<MacroArg> macroArgs, RuntimeServices rsvc) 277 { 278 StringBuilder ret = (buf == null) ? new StringBuilder() : buf; 279 280 ret.append(rsvc.getParserConfiguration().getHashChar()).append(macroArgs.get(0).name).append("( "); 281 for (MacroArg marg : macroArgs) 282 { 283 ret.append(rsvc.getParserConfiguration().getDollarChar()).append(marg.name); 284 if (marg.defaultVal != null) 285 { 286 ret.append("=").append(marg.defaultVal); 287 } 288 ret.append(' '); 289 } 290 ret.append(" )"); 291 return ret; 292 } 293 294 } 295