geirm 01/04/08 17:04:50
Added: whiteboard/geir/listener ASTReference.java
ASTSetDirective.java
Log:
ASTReference and ASTSetDirective that use the EventCartridge.... for
discussion
Revision Changes Path
1.1 jakarta-velocity/whiteboard/geir/listener/ASTReference.java
Index: ASTReference.java
===================================================================
package org.apache.velocity.runtime.parser.node;
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2001 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Velocity", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact [EMAIL PROTECTED]
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
import java.io.Writer;
import java.io.IOException;
import java.util.Map;
import java.lang.reflect.Method;
import org.apache.velocity.context.Context;
import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.runtime.Runtime;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.exception.ReferenceException;
import org.apache.velocity.runtime.parser.*;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.context.EventCartridge;
import org.apache.velocity.context.ReferenceInsertionEventHandler;
import org.apache.velocity.context.NullReferenceEventHandler;
/**
* This class is responsible for handling the references in
* VTL ($foo).
*
* Please look at the Parser.jjt file which is
* what controls the generation of this class.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Jason van Zyl</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Geir Magnusson Jr.</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Christoph Reck</a>
* @version $Id: ASTReference.java,v 1.1 2001/04/09 00:04:49 geirm Exp $
*/
public class ASTReference extends SimpleNode
{
/* Reference types */
private static final int NORMAL_REFERENCE = 1;
private static final int FORMAL_REFERENCE = 2;
private static final int QUIET_REFERENCE = 3;
private int referenceType;
private String nullString;
private String rootString;
private boolean escaped = false;
private boolean computableReference = true;
private String prefix = "";
public ASTReference(int id)
{
super(id);
}
public ASTReference(Parser p, int id)
{
super(p, id);
}
/** Accept the visitor. **/
public Object jjtAccept(ParserVisitor visitor, Object data)
{
return visitor.visit(this, data);
}
public Object init( InternalContextAdapter context, Object data) throws Exception
{
/*
* init our children
*/
super.init( context, data );
/*
* the only thing we can do in init() is getRoot()
* as that is template based, not context based,
* so it's thread- and context-safe
*/
rootString = getRoot();
return data;
}
/**
* gets an Object that 'is' the value of the reference
*
* @param o unused Object parameter
* @param context context used to generate value
*/
public Object execute(Object o, InternalContextAdapter context)
throws MethodInvocationException
{
/*
* get the root object from the context
*/
Object result = getVariableValue(context, rootString);
if (result == null)
{
return null;
}
/*
* Iteratively work 'down' (it's flat...) the reference
* to get the value, but check to make sure that
* every result along the path is valid. For example:
*
* $hashtable.Customer.Name
*
* The $hashtable may be valid, but there is no key
* 'Customer' in the hashtable so we want to stop
* when we find a null value and return the null
* so the error gets logged.
*/
int children = jjtGetNumChildren();
try
{
for (int i = 0; i < children; i++)
{
result = jjtGetChild(i).execute(result,context);
if (result == null)
{
return null;
}
}
return result;
}
catch( MethodInvocationException mie)
{
/*
* someone tossed their cookies
*/
Runtime.error("Method " + mie.getMethodName() + " threw exception for
reference $"
+ rootString
+ " in template " + context.getCurrentTemplateName()
+ " at " + " [" + this.getLine() + "," + this.getColumn()
+ "]");
mie.setReferenceName( rootString );
throw mie;
}
}
/**
* gets the value of the reference and outputs it to the
* writer.
*
* @param context context of data to use in getting value
* @param writer writer to render to
*/
public boolean render( InternalContextAdapter context, Writer writer)
throws IOException, MethodInvocationException
{
Object value = execute(null, context);
/*
* if this reference is escaped (\$foo) then we want to do one of two
things :
* 1) if this is a reference in the context, then we want to print $foo
* 2) if not, then \$foo (its considered shmoo, not VTL)
*/
if ( escaped )
{
if ( value == null )
writer.write( NodeUtils.specialText(getFirstToken()) + prefix + "\\"
+ nullString );
else
writer.write( NodeUtils.specialText(getFirstToken()) + prefix +
nullString );
return true;
}
/*
* the normal processing
*/
EventCartridge ec = context.getEventCartridge();
if (value == null)
{
String localNullstring = nullString;
if (ec != null)
{
Object[] rieh = ec.getNullReferenceHandlerArray();
for(int i = 0; i < rieh.length; i++)
{
localNullstring = ( (NullReferenceEventHandler)
rieh[i]).nullReferenceRender( localNullstring );
}
}
/*
* write prefix twice, because it's shmoo, so the \ don't escape each
other...
*/
writer.write(NodeUtils.specialText(getFirstToken()) + prefix + prefix +
localNullstring);
if (referenceType != QUIET_REFERENCE && Runtime.getBoolean(
RuntimeConstants.RUNTIME_LOG_REFERENCE_LOG_INVALID, true) )
Runtime.warn(new ReferenceException("reference : template = " +
context.getCurrentTemplateName(), this));
}
else
{
Object val = value;
if (ec != null)
{
Object[] rieh = ec.getReferenceInsertionHandlerArray();
for(int i = 0; i < rieh.length; i++)
{
val = ( (ReferenceInsertionEventHandler)
rieh[i]).referenceInsert( nullString, val );
}
}
String s = NodeUtils.specialText(getFirstToken()) + prefix +
val.toString();
writer.write( s);
}
return true;
}
/**
* Computes boolean value of this reference
* Returns the actual value of reference return type
* boolean, and 'true' if value is not null
*
* @param context context to compute value with
*/
public boolean evaluate( InternalContextAdapter context)
throws MethodInvocationException
{
Object value = execute(null, context);
if (value == null)
return false;
else if (value instanceof Boolean)
{
if (((Boolean) value).booleanValue())
return true;
else
return false;
}
else
return true;
}
public Object value( InternalContextAdapter context)
throws MethodInvocationException
{
return ( computableReference ? execute(null, context) : null );
}
/**
* Sets the value of a complex reference (something like $foo.bar)
* Currently used by ASTSetReference()
*
* @see ASTSetDirective
*
* @param context context object containing this reference
* @param value Object to set as value
* @return true if successful, false otherwise
*/
public boolean setValue( InternalContextAdapter context, Object value)
throws MethodInvocationException
{
/*
* The rootOfIntrospection is the object we will
* retrieve from the Context. This is the base
* object we will apply reflection to.
*/
Object result = getVariableValue(context, rootString);
if (result == null)
{
Runtime.error(new ReferenceException("reference set : template = " +
context.getCurrentTemplateName(), this));
return false;
}
/*
* How many child nodes do we have?
*/
int children = jjtGetNumChildren();
for (int i = 0; i < children - 1; i++)
{
result = jjtGetChild(i).execute(result, context);
if (result == null)
{
Runtime.error(new ReferenceException("reference set : template = " +
context.getCurrentTemplateName(), this));
return false;
}
}
/*
* We support two ways of setting the value in a #set($ref.foo = $value ) :
* 1) ref.setFoo( value )
* 2) ref,put("foo", value ) to parallel the get() map introspection
*/
String identifier = jjtGetChild(children - 1).getFirstToken().image;
try
{
/*
* first, we introspect for the set<identifier> setter method
*/
Class[] params = { value.getClass() };
Class c = result.getClass();
Method m = null;
try
{
m = c.getMethod("set" + identifier, params);
}
catch( NoSuchMethodException nsme2)
{
StringBuffer sb = new StringBuffer( "set" );
sb.append( identifier );
if( Character.isLowerCase( sb.charAt(3)))
{
sb.setCharAt( 3 , Character.toUpperCase( sb.charAt( 3 ) ) );
}
else
{
sb.setCharAt( 3 , Character.toLowerCase( sb.charAt( 3 ) ) );
}
m = c.getMethod( sb.toString(), params);
}
/*
* and if we get here, getMethod() didn't chuck an exception...
*/
Object[] args = { value };
m.invoke(result, args);
}
catch (NoSuchMethodException nsme)
{
/*
* right now, we only support the Map interface
*/
if (result instanceof Map)
{
try
{
((Map) result).put(identifier, value);
}
catch (Exception ex)
{
Runtime.error("ASTReference Map.put : exception : " + ex
+ " template = " +
context.getCurrentTemplateName()
+ " [" + this.getLine() + "," + this.getColumn() +
"]");
return false;
}
}
else
{
Runtime.error("ASTReference : cannot find " + identifier + " as
settable property or key to Map in"
+ " template = " + context.getCurrentTemplateName()
+ " [" + this.getLine() + "," + this.getColumn() +
"]");
return false;
}
}
catch( Exception e )
{
/*
* maybe a security exception?
*/
Runtime.error("ASTReference setValue() : exception : " + e
+ " template = " +
context.getCurrentTemplateName()
+ " [" + this.getLine() + "," + this.getColumn() +
"]");
return false;
}
return true;
}
private String getRoot()
{
Token t = getFirstToken();
/*
* we have a special case where something like
* $(\\)*!, where the user want's to see something
* like $!blargh in the output, but the ! prevents it from showing.
* I think that at this point, this isn't a reference.
*/
/* so, see if we have "\\!" */
int slashbang = t.image.indexOf("\\!");
if ( slashbang != -1 )
{
/*
* lets do all the work here. I would argue that if this occurrs, it's
* not a reference at all, so preceeding \ characters in front of the $
* are just schmoo. So we just do the escape processing trick (even |
odd)
* and move on. This kind of breaks the rule pattern of $ and # but
'!' really
* tosses a wrench into things.
*/
/*
* count the escapes : even # -> not escaped, odd -> escaped
*/
int i = 0;
int len = t.image.length();
i = t.image.indexOf("$");
if (i == -1)
{
/* yikes! */
Runtime.error("ASTReference.getRoot() : internal error : no $ found
for slashbang.");
computableReference = false;
nullString = t.image;
return nullString;
}
while( i < len && t.image.charAt(i) != '\\')
i++;
/* ok, i is the first \ char */
int start = i;
int count = 0;
while( i < len && t.image.charAt(i++) == '\\' )
count++;
/*
* now construct the output string. We really don't care about leading
* slashes as this is not a reference. It's quasi-schmoo
*/
nullString = t.image.substring(0,start); // prefix up to the first
nullString += t.image.substring(start, start + count-1 ); // get the
slashes
nullString += t.image.substring(start+count); // and the rest, including
the
/*
* this isn't a valid reference, so lets short circuit the value and
set calcs
*/
computableReference = false;
return nullString;
}
/*
* we need to see if this reference is escaped. if so
* we will clean off the leading \'s and let the
* regular behavior determine if we should output this
* as \$foo or $foo later on in render(). Lazyness..
*/
escaped = false;
if ( t.image.startsWith("\\"))
{
/*
* count the escapes : even # -> not escaped, odd -> escaped
*/
int i = 0;
int len = t.image.length();
while( i < len && t.image.charAt(i) == '\\' )
i++;
if ( (i % 2) != 0 )
escaped = true;
if (i > 0)
prefix = t.image.substring(0, i / 2 );
t.image = t.image.substring(i);
}
/*
* get the literal in case this reference isn't backed by the context at
runtime
*/
nullString = literal();
if (t.image.startsWith("$!"))
{
referenceType = QUIET_REFERENCE;
/*
* only if we aren't escaped do we want to null the output
*/
if (!escaped)
nullString = "";
if (t.image.startsWith("$!{"))
{
/*
* ex : $!{provider.Title}
*/
return t.next.image;
}
else
{
/*
* ex : $!provider.Title
*/
return t.image.substring(2);
}
}
else if (t.image.equals("${"))
{
/*
* ex : ${provider.Title}
*/
referenceType = FORMAL_REFERENCE;
return t.next.image;
}
else
{
/*
* ex : $provider.Title
*/
referenceType = NORMAL_REFERENCE;
return t.image.substring(1);
}
}
public Object getVariableValue(Context context, String variable)
{
return context.get(variable);
}
}
1.1 jakarta-velocity/whiteboard/geir/listener/ASTSetDirective.java
Index: ASTSetDirective.java
===================================================================
package org.apache.velocity.runtime.parser.node;
/* Generated By:JJTree: Do not edit this line. ASTSetDirective.java */
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2001 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Velocity", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact [EMAIL PROTECTED]
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
import java.io.IOException;
import java.io.Writer;
import org.apache.velocity.context.InternalContextAdapter;
import org.apache.velocity.runtime.Runtime;
import org.apache.velocity.runtime.exception.ReferenceException;
import org.apache.velocity.runtime.parser.Parser;
import org.apache.velocity.runtime.parser.Token;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.context.NullSetEventHandler;
import org.apache.velocity.context.EventCartridge;
/**
* Node for the #set directive
*
* @author <a href="mailto:[EMAIL PROTECTED]">Jason van Zyl</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Geir Magnusson Jr.</a>
* @version $Id: ASTSetDirective.java,v 1.1 2001/04/09 00:04:49 geirm Exp $
*/
public class ASTSetDirective extends SimpleNode
{
private Node right;
private ASTReference left;
boolean blather = false;
public ASTSetDirective(int id)
{
super(id);
}
public ASTSetDirective(Parser p, int id)
{
super(p, id);
}
/** Accept the visitor. **/
public Object jjtAccept(ParserVisitor visitor, Object data)
{
return visitor.visit(this, data);
}
/**
* simple init. We can get the RHS and LHS as the the tree structure is static
*/
public Object init( InternalContextAdapter context, Object data) throws Exception
{
/*
* init the tree correctly
*/
super.init( context, data );
right = getRightHandSide();
left = getLeftHandSide();
blather = Runtime.getBoolean(Runtime.RUNTIME_LOG_REFERENCE_LOG_INVALID,
true);
return data;
}
/**
* puts the value of the RHS into the context under the key of the LHS
*/
public boolean render( InternalContextAdapter context, Writer writer)
throws IOException, MethodInvocationException
{
/*
* get the RHS node, and it's value
*/
Object value = right.value(context);
/*
* it's an error if we don't have a value of some sort
*/
if ( value == null)
{
if ( blather )
{
EventCartridge ec = context.getEventCartridge();
if (ec != null)
{
Object[] rieh = ec.getNullSetEventHandlerArray();
boolean doit = true;
for(int i = 0; i < rieh.length; i++)
{
if( !( (NullSetEventHandler) rieh[i]).nullSetLogMessage(
left.literal() ))
doit = false;
}
if (doit)
Runtime.error("RHS of #set statement is null. Context will
not be modified. "
+ context.getCurrentTemplateName() + " [line "
+ getLine()
+ ", column " + getColumn() + "]");
}
}
return false;
}
/*
* if the LHS is simple, just punch the value into the context
* otherwise, use the setValue() method do to it.
* Maybe we should always use setValue()
*/
if (left.jjtGetNumChildren() == 0)
context.put(left.getFirstToken().image.substring(1), value);
else
left.setValue(context, value);
return true;
}
/**
* returns the ASTReference that is the LHS of the set statememt
*/
private ASTReference getLeftHandSide()
{
return (ASTReference) jjtGetChild(0).jjtGetChild(0).jjtGetChild(0);
}
/**
* returns the RHS Node of the set statement
*/
private Node getRightHandSide()
{
return jjtGetChild(0).jjtGetChild(0).jjtGetChild(1).jjtGetChild(0);
}
}