blob: 99cb1f7a41f98f0b12a51be7e9dbe2f6ae828f28 [file] [log] [blame]
package org.apache.velocity.context;
/*
* 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.StringWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.apache.velocity.app.event.EventCartridge;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.VelocityException;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.parser.ParserTreeConstants;
import org.apache.velocity.runtime.parser.node.ASTReference;
import org.apache.velocity.runtime.parser.node.Node;
import org.apache.velocity.runtime.parser.node.ASTBlock;
import org.apache.velocity.runtime.resource.Resource;
import org.apache.velocity.util.introspection.IntrospectionCacheData;
/**
* Context for Velocity macro arguments.
*
* This special context combines ideas of earlier VMContext and VMProxyArgs
* by implementing routing functionality internally. This significantly
* reduces memory allocation upon macro invocations.
* Since the macro AST is now shared and RuntimeMacro directive is used,
* the earlier implementation of precalculating VMProxyArgs would not work.
*
* See <a href="http://issues.apache.org/jira/browse/VELOCITY-607">Issue 607</a>
* for more info on this class.
* @author <a href="mailto:wyla@removeme.sci.fi">Jarkko Viinamaki</a>
* @version $Id$
* @since 1.6
*/
public class ProxyVMContext extends ChainedInternalContextAdapter
{
/** container for our macro AST node arguments. Size must be power of 2. */
Map vmproxyhash = new HashMap(8, 0.8f);
/** container for any local or constant macro arguments. Size must be power of 2. */
Map localcontext = new HashMap(8, 0.8f);
/** support for local context scope feature, where all references are local */
private boolean localContextScope;
/** needed for writing log entries. */
private RuntimeServices rsvc;
/**
* @param inner Velocity context for processing
* @param rsvc RuntimeServices provides logging reference
* @param localContextScope if true, all references are set to be local
*/
public ProxyVMContext(InternalContextAdapter inner,
RuntimeServices rsvc,
boolean localContextScope)
{
super(inner);
this.localContextScope = localContextScope;
this.rsvc = rsvc;
}
/**
* Used to put Velocity macro arguments into this context.
*
* @param context rendering context
* @param macroArgumentName name of the macro argument that we received
* @param literalMacroArgumentName ".literal.$"+macroArgumentName
* @param argumentValue actual value of the macro argument
*
* @throws MethodInvocationException
*/
public void addVMProxyArg(InternalContextAdapter context,
String macroArgumentName,
String literalMacroArgumentName,
Node argumentValue) throws MethodInvocationException
{
if (isConstant(argumentValue))
{
localcontext.put(macroArgumentName, argumentValue.value(context));
}
else
{
vmproxyhash.put(macroArgumentName, argumentValue);
localcontext.put(literalMacroArgumentName, argumentValue);
}
}
/**
* AST nodes that are considered constants can be directly
* saved into the context. Dynamic values are stored in
* another argument hashmap.
*
* @param node macro argument as AST node
* @return true if the node is a constant value
*/
private boolean isConstant(Node node)
{
switch (node.getType())
{
case ParserTreeConstants.JJTINTEGERRANGE:
case ParserTreeConstants.JJTREFERENCE:
case ParserTreeConstants.JJTOBJECTARRAY:
case ParserTreeConstants.JJTMAP:
case ParserTreeConstants.JJTSTRINGLITERAL:
case ParserTreeConstants.JJTTEXT:
case ParserTreeConstants.JJTBLOCK:
return (false);
default:
return (true);
}
}
/**
* Impl of the Context.put() method.
*
* @param key name of item to set
* @param value object to set to key
* @return old stored object
*/
public Object put(final String key, final Object value)
{
return put(key, value, localContextScope);
}
/**
* Allows callers to explicitly put objects in the local context, no matter what the
* velocimacro.context.local setting says. Needed e.g. for loop variables in foreach.
*
* @param key name of item to set.
* @param value object to set to key.
* @return old stored object
*/
public Object localPut(final String key, final Object value)
{
return put(key, value, true);
}
/**
* Internal put method to select between local and global scope.
*
* @param key name of item to set
* @param value object to set to key
* @param forceLocal True forces the object into the local scope.
* @return old stored object
*/
protected Object put(final String key, final Object value, final boolean forceLocal)
{
Object old = localcontext.put(key, value);
if (!forceLocal)
{
old = super.put(key, value);
}
return old;
}
/**
* Implementation of the Context.get() method. First checks
* localcontext, then arguments, then global context.
*
* @param key name of item to get
* @return stored object or null
*/
public Object get(String key)
{
Object o = localcontext.get(key);
if (o != null)
{
return o;
}
Node astNode = (Node) vmproxyhash.get(key);
if (astNode != null)
{
int type = astNode.getType();
// if the macro argument (astNode) is a reference, we need to evaluate it
// in case it is a multilevel node
if (type == ParserTreeConstants.JJTREFERENCE)
{
ASTReference ref = (ASTReference) astNode;
if (ref.jjtGetNumChildren() > 0)
{
return ref.execute(null, innerContext);
}
else
{
Object obj = innerContext.get(ref.getRootString());
if (obj == null && ref.strictRef)
{
if (!innerContext.containsKey(ref.getRootString()))
{
throw new MethodInvocationException("Parameter '" + ref.getRootString()
+ "' not defined", null, key, ref.getTemplateName(),
ref.getLine(), ref.getColumn());
}
}
return obj;
}
}
else if (type == ParserTreeConstants.JJTBLOCK)
{
// this happens for #@someMacro($arg1 $arg2) bodyAST #end calls
try
{
// astNode is actually BlockMacro.BlockMacroContainer which contains a Writer internally although
// we seem to pass null here
astNode.render(innerContext, null);
// return an empty string because the Node already rendered all content
return "";
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
String msg = "ProxyVMContext.get() : error rendering reference";
rsvc.getLog().error(msg, e);
throw new VelocityException(msg, e);
}
}
else if (type == ParserTreeConstants.JJTTEXT)
{
// this really shouldn't happen. text is just a throwaway arg for #foreach()
try
{
StringWriter writer = new StringWriter();
astNode.render(innerContext, writer);
return writer.toString();
}
catch (RuntimeException e)
{
throw e;
}
catch (Exception e)
{
String msg = "ProxyVMContext.get() : error rendering reference";
rsvc.getLog().error(msg, e);
throw new VelocityException(msg, e);
}
}
else
{
// use value method to render other dynamic nodes
return astNode.value(innerContext);
}
}
return super.get(key);
}
/**
* @see org.apache.velocity.context.Context#containsKey(java.lang.Object)
*/
public boolean containsKey(Object key)
{
return vmproxyhash.containsKey(key)
|| localcontext.containsKey(key)
|| super.containsKey(key);
}
/**
* @see org.apache.velocity.context.Context#getKeys()
*/
public Object[] getKeys()
{
if (localcontext.isEmpty())
{
return vmproxyhash.keySet().toArray();
}
else if (vmproxyhash.isEmpty())
{
return localcontext.keySet().toArray();
}
HashSet keys = new HashSet(localcontext.keySet());
keys.addAll(vmproxyhash.keySet());
return keys.toArray();
}
/**
* @see org.apache.velocity.context.Context#remove(java.lang.Object)
*/
public Object remove(Object key)
{
Object loc = localcontext.remove(key);
Object glo = null;
vmproxyhash.remove(key);
if (!localContextScope)
{
glo = super.remove(key);
}
if (loc != null)
{
return loc;
}
return glo;
}
}