bboss.org.apache.velocity.runtime.directive.Foreach Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bboss-velocity Show documentation
Show all versions of bboss-velocity Show documentation
bboss is a j2ee framework include aop/ioc,mvc,persistent,taglib,rpc,event ,bean-xml serializable and so on.http://www.bbossgroups.com
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.Iterator;
import bboss.org.apache.velocity.context.ChainedInternalContextAdapter;
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.RuntimeConstants;
import bboss.org.apache.velocity.runtime.RuntimeServices;
import bboss.org.apache.velocity.runtime.log.Log;
import bboss.org.apache.velocity.runtime.parser.node.ASTReference;
import bboss.org.apache.velocity.runtime.parser.node.Node;
import bboss.org.apache.velocity.runtime.parser.node.SimpleNode;
import bboss.org.apache.velocity.util.introspection.Info;
/**
* Foreach directive used for moving through arrays,
* or objects that provide an Iterator.
*
* @author Jason van Zyl
* @author Geir Magnusson Jr.
* @author Daniel Rall
* @version $Id: Foreach.java 945927 2010-05-18 22:21:41Z nbubna $
*/
public class Foreach extends Directive
{
/**
* A special context to use when the foreach iterator returns a null. This
* is required since the standard context may not support nulls.
* All puts and gets are passed through, except for the foreach iterator key.
* @since 1.5
*/
protected static class NullHolderContext extends ChainedInternalContextAdapter
{
private String loopVariableKey = "";
private boolean active = true;
/**
* Create the context as a wrapper to be used within the foreach
* @param key the reference used in the foreach
* @param context the parent context
*/
private NullHolderContext( String key, InternalContextAdapter context )
{
super(context);
if( key != null )
loopVariableKey = key;
}
/**
* Get an object from the context, or null if the key is equal to the loop variable
* @see bboss.org.apache.velocity.context.InternalContextAdapter#get(java.lang.String)
* @exception MethodInvocationException passes on potential exception from reference method call
*/
public Object get( String key ) throws MethodInvocationException
{
return ( active && loopVariableKey.equals(key) )
? null
: super.get(key);
}
/**
* @see bboss.org.apache.velocity.context.InternalContextAdapter#put(java.lang.String key, java.lang.Object value)
*/
public Object put( String key, Object value )
{
if( loopVariableKey.equals(key) && (value == null) )
{
active = true;
}
return super.put( key, value );
}
/**
* Allows callers to explicitly put objects in the local context.
* Objects added to the context through this method always end up
* in the top-level context of possible wrapped contexts.
*
* @param key name of item to set.
* @param value object to set to key.
* @see bboss.org.apache.velocity.context.InternalWrapperContext#localPut(String, Object)
*/
public Object localPut(final String key, final Object value)
{
return put(key, value);
}
/**
* Remove an object from the context
* @see bboss.org.apache.velocity.context.InternalContextAdapter#remove(java.lang.Object key)
*/
public Object remove(Object key)
{
if( loopVariableKey.equals(key) )
{
active = false;
}
return super.remove(key);
}
}
/**
* Return name of this directive.
* @return The name of this directive.
*/
public String getName()
{
return "foreach";
}
/**
* Return type of this directive.
* @return The type of this directive.
*/
public int getType()
{
return BLOCK;
}
/**
* The name of the variable to use when placing
* the counter value into the context. Right
* now the default is $velocityCount.
*/
private String counterName;
/**
* The name of the variable to use when placing
* iterator hasNext() value into the context.Right
* now the defailt is $velocityHasNext
*/
private String hasNextName;
/**
* What value to start the loop counter at.
*/
private int counterInitialValue;
/**
* The maximum number of times we're allowed to loop.
*/
private int maxNbrLoops;
/**
* Whether or not to throw an Exception if the iterator is null.
*/
private boolean skipInvalidIterator;
/**
* The reference name used to access each
* of the elements in the list object. It
* is the $item in the following:
*
* #foreach ($item in $list)
*
* This can be used class wide because
* it is immutable.
*/
private String elementKey;
// track if we've done the deprecation warning thing already
private boolean warned = false;
/**
* immutable, so create in init
*/
protected Info uberInfo;
/**
* simple init - init the tree and get the elementKey from
* the AST
* @param rs
* @param context
* @param node
* @throws TemplateInitException
*/
public void init(RuntimeServices rs, InternalContextAdapter context, Node node)
throws TemplateInitException
{
super.init(rs, context, node);
// handle deprecated config settings
counterName = rsvc.getString(RuntimeConstants.COUNTER_NAME);
hasNextName = rsvc.getString(RuntimeConstants.HAS_NEXT_NAME);
counterInitialValue = rsvc.getInt(RuntimeConstants.COUNTER_INITIAL_VALUE);
// only warn once per instance...
if (!warned && rsvc.getLog().isWarnEnabled())
{
warned = true;
// ...and only if they customize these settings
if (!"velocityCount".equals(counterName))
{
rsvc.getLog().warn("The "+RuntimeConstants.COUNTER_NAME+
" property has been deprecated. It will be removed"+
" (along with $velocityCount itself) in Velocity 2.0. "+
" Instead, please use $foreach.count to access"+
" the loop counter.");
}
if (!"velocityHasNext".equals(hasNextName))
{
rsvc.getLog().warn("The "+RuntimeConstants.HAS_NEXT_NAME+
" property has been deprecated. It will be removed"+
" (along with $velocityHasNext itself ) in Velocity 2.0. "+
" Instead, please use $foreach.hasNext to access"+
" this value from now on.");
}
if (counterInitialValue != 1)
{
/** rsvc.getLog().warn("The "+RuntimeConstants.COUNTER_INITIAL_VALUE+
" property has been deprecated. It will be removed"+
" (along with $velocityCount itself) in Velocity 2.0. "+
" Instead, please use $foreach.index to access"+
" the 0-based loop index and $foreach.count"+
" to access the 1-based loop counter.");*/
}
}
maxNbrLoops = rsvc.getInt(RuntimeConstants.MAX_NUMBER_LOOPS,
Integer.MAX_VALUE);
if (maxNbrLoops < 1)
{
maxNbrLoops = Integer.MAX_VALUE;
}
skipInvalidIterator =
rsvc.getBoolean(RuntimeConstants.SKIP_INVALID_ITERATOR, true);
if (rsvc.getBoolean(RuntimeConstants.RUNTIME_REFERENCES_STRICT, false))
{
// If we are in strict mode then the default for skipInvalidItarator
// is true. However, if the property is explicitly set, then honor the setting.
skipInvalidIterator = rsvc.getBoolean(RuntimeConstants.SKIP_INVALID_ITERATOR, false);
}
/*
* this is really the only thing we can do here as everything
* else is context sensitive
*/
SimpleNode sn = (SimpleNode) node.jjtGetChild(0);
if (sn instanceof ASTReference)
{
elementKey = ((ASTReference) sn).getRootString();
}
else
{
/*
* the default, error-prone way which we'll remove
* TODO : remove if all goes well
*/
elementKey = sn.getFirstToken().image.substring(1);
}
/*
* make an uberinfo - saves new's later on
*/
uberInfo = new Info(this.getTemplateName(),
getLine(),getColumn());
}
/**
* Extension hook to allow subclasses to control whether loop vars
* are set locally or not. So, those in favor of VELOCITY-285, can
* make that happen easily by overriding this and having it use
* context.localPut(k,v). See VELOCITY-630 for more on this.
*/
protected void put(InternalContextAdapter context, String key, Object value)
{
context.put(key, value);
}
/**
* renders the #foreach() block
* @param context
* @param writer
* @param node
* @return True if the directive rendered successfully.
* @throws IOException
* @throws MethodInvocationException
* @throws ResourceNotFoundException
* @throws ParseErrorException
*/
public boolean render(InternalContextAdapter context,
Writer writer, Node node)
throws IOException, MethodInvocationException, ResourceNotFoundException,
ParseErrorException
{
/*
* do our introspection to see what our collection is
*/
Object listObject = node.jjtGetChild(2).value(context);
if (listObject == null)
return false;
Iterator i = null;
try
{
i = rsvc.getUberspect().getIterator(listObject, uberInfo);
}
/**
* pass through application level runtime exceptions
*/
catch( RuntimeException e )
{
throw e;
}
catch(Exception ee)
{
String msg = "Error getting iterator for #foreach at "+uberInfo;
rsvc.getLog().error(msg, ee);
throw new VelocityException(msg, ee);
}
if (i == null)
{
if (skipInvalidIterator)
{
return false;
}
else
{
Node pnode = node.jjtGetChild(2);
String msg = "#foreach parameter " + pnode.literal() + " at "
+ Log.formatFileString(pnode)
+ " is of type " + listObject.getClass().getName()
+ " and is either of wrong type or cannot be iterated.";
rsvc.getLog().error(msg);
throw new VelocityException(msg);
}
}
int counter = counterInitialValue;
boolean maxNbrLoopsExceeded = false;
/*
* save the element key if there is one, and the loop counter
*/
Object o = context.get(elementKey);
Object savedCounter = context.get(counterName);
Object nextFlag = context.get(hasNextName);
/*
* roll our own scope class instead of using preRender(ctx)'s
*/
ForeachScope foreach = null;
if (isScopeProvided())
{
String name = getScopeName();
foreach = new ForeachScope(this, context.get(name));
context.put(name, foreach);
}
/*
* Instantiate the null holder context if a null value
* is returned by the foreach iterator. Only one instance is
* created - it's reused for every null value.
*/
NullHolderContext nullHolderContext = null;
while (!maxNbrLoopsExceeded && i.hasNext())
{
// TODO: JDK 1.5+ -> Integer.valueOf()
put(context, counterName , new Integer(counter));
Object value = i.next();
put(context, hasNextName, Boolean.valueOf(i.hasNext()));
put(context, elementKey, value);
if (isScopeProvided())
{
// update the scope control
foreach.index++;
foreach.hasNext = i.hasNext();
}
try
{
/*
* If the value is null, use the special null holder context
*/
if (value == null)
{
if (nullHolderContext == null)
{
// lazy instantiation
nullHolderContext = new NullHolderContext(elementKey, context);
}
node.jjtGetChild(3).render(nullHolderContext, writer);
}
else
{
node.jjtGetChild(3).render(context, writer);
}
}
catch (StopCommand stop)
{
if (stop.isFor(this))
{
break;
}
else
{
// clean up first
clean(context, o, savedCounter, nextFlag);
throw stop;
}
}
counter++;
// Determine whether we're allowed to continue looping.
// ASSUMPTION: counterInitialValue is not negative!
maxNbrLoopsExceeded = (counter - counterInitialValue) >= maxNbrLoops;
}
clean(context, o, savedCounter, nextFlag);
return true;
}
protected void clean(InternalContextAdapter context,
Object o, Object savedCounter, Object nextFlag)
{
/*
* restores element key if exists
* otherwise just removes
*/
if (o != null)
{
context.put(elementKey, o);
}
else
{
context.remove(elementKey);
}
/*
* restores the loop counter (if we were nested)
* if we have one, else just removes
*/
if (savedCounter != null)
{
context.put(counterName, savedCounter);
}
else
{
context.remove(counterName);
}
/*
* restores the "hasNext" boolean flag if it exists
*/
if (nextFlag != null)
{
context.put(hasNextName, nextFlag);
}
else
{
context.remove(hasNextName);
}
// clean up after the ForeachScope
postRender(context);
}
}