Optimising Variable to reduce instantiation overhead
Alex Twisleton-Wykeham-Fiennes <[email protected]> Sun, 12 Mar 2006 18:50:17 +0000
| Newsgroups | gmane.comp.java.webmacro.user |
|---|---|
| Message-ID | <[email protected]> |
All, more performance tweaks, this one to the org.webmacro.engine.Variable class. performance sampling showed a large proportion of CPU time being allocated to the makeName(Object[] names) class that is invoked in the constructor to create the human-readable version of the Object[] that stores the names, which is then stored in _vname. However, _vname is only ever used in situations when you generating PropertyException Objects to provide sensible error messages, and for all "correctly functioning" instances, this is completely redundant. What I've done is:- - disabled the call to makeName(names) in the constructor - made the getVariableName() method check to see if _vname has been initialised, and if not then build the name before returning it. This is now synchronized as it changes the state of the object. - made all previous references to _vname in the Variable class invoke getVariableName() rather than accessing the variable directly. - made _vname private rather than protected thereby forcing sub-classes to access it via getVariableName() The resulting class (attached) passes all the unit tests and completely removes this overhead for all normal usage patterns. Alex
Variable.java
(text/x-java, 10.6 KB)
/*
* Copyright (C) 1998-2000 Semiotek Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted under the terms of either of the following
* Open Source licenses:
*
* The GNU General Public License, version 2, or any later version, as
* published by the Free Software Foundation
* (http://www.fsf.org/copyleft/gpl.html);
*
* or
*
* The Semiotek Public License (http://webmacro.org/LICENSE.)
*
* This software is provided "as is", with NO WARRANTY, not even the
* implied warranties of fitness to purpose, or merchantability. You
* assume all risks and liabilities associated with its use.
*
* See www.webmacro.org for more information on the WebMacro project.
*/
package org.webmacro.engine;
import org.webmacro.*;
import org.webmacro.util.Named;
import java.io.IOException;
// PRIMARY CLASS: Variable
/**
* A Variable is a reference into a Propertymap.
* <p>
* A variable name contains a list of names separated by dots, for
* example "$User.Identity.email.address" is the list: User, Identity,
* email, and address.
* <p>
* PLEASE NOTE: Case-sensitivity is enforced. "User" is the the same
* name as "user".
* <p>
* What that means: When a template is interpreted, it is interpreted
* in terms of data in a hashtable/map called the "context". This is
* actually a Map of type Map. The context contains all the
* local variables that have been set, as well as other information
* that Macros may use to evaluate the request.
* <p>
* Variable depends heavily on Property introspection: It is defined
* as a list of one or more names (separated by dots when written).
* <p>
* Those names are references to sub-objects within the context. The
* Variable instance, when interpreted, will decend through the context
* following fields, method references, or hash table look-ups based
* on its names.
* <p>
* For example, the variable "$User.Identity.email.address" implies
* that there is a "User" object under the Map--either it is
* a field within the map, or the map has a getUser() method, or
* the User can be obtained by calling Map.get("User").
* <p>
* The full expansion of $User.Identity.email.address might be:<pre>
*
* Map.get("User").getIdentity().get("email").address
*
* </pre>. Variable (actually the Property class it uses) will figure
* out how to decend through the object like this until it finds the
* final reference--which is the "value" of the variable.
* <p>
* When searchin for subfields Variable prefers fields over getFoo()
* methods, and getFoo() over get("Foo").
*
*/
public abstract class Variable implements Macro, Visitable
{
// null: because in BuildContext.getVariableType() we can just
// return null from a HashMap for a never before heard of variable
// to mean that it is a PROPERTY_TYPE. Only this code right here
// and BuildContext.getVariableType() needs to know that.
final static public Object PROPERTY_TYPE = new Object();
final static public Object LOCAL_TYPE = new Object();
/**
* The name of this variable.
*/
private String _vname;
/**
* The name as an array
*/
protected Object[] _names;
/**
* Create a variable with the supplied name. The elements of the name
* are either strings, or a method reference.
*/
Variable (Object names[])
{
_names = names;
}
/**
* Return the property names for this variable. These are stringified
* names corresponding to the names of the variable; if one of the
* elements of the variable name is a method call then the name of
* the method is inserted at that point as if it were a property name.
*/
static final String[] makePropertyNames (Object names[])
{
String[] sn = new String[names.length];
for (int i = 0; i < sn.length; i++)
{
sn[i] = (names[i] instanceof Named) ?
((Named) names[i]).getName() : (String) names[i];
}
return sn;
}
public final String[] getPropertyNames ()
{
return makePropertyNames(_names);
}
/**
* Like getPropertyNames, but only works if isSimpleName is true
*/
public final String getName ()
{
return (_names[0] instanceof Named) ?
((Named) _names[0]).getName()
: (String) _names[0];
}
/**
* Returns true if the Variable describes a simple name (one with only
* one element)
*/
public boolean isSimpleName ()
{
return (_names.length == 1);
}
/**
* Looks in the hashTable (context) for a value keyed to this variables
* name and returns the value string. If the resulting value is a Macro,
* recursively call its evaluate method.
* @return String
*/
final public Object evaluate (Context context) throws PropertyException
{
try
{
Object val = getValue(context);
if (val instanceof Macro)
{
val = ((Macro) val).evaluate(context); // recurse
}
return val;
}
catch (NullPointerException e)
{
// May throw
context.getEvaluationExceptionHandler()
.evaluate(this, context,
new PropertyException.NullValueException(getVariableName()));
return null;
}
catch (PropertyException e)
{
// May throw
if (e instanceof PropertyException.UndefinedVariableException)
{
PropertyException.UndefinedVariableException uve = (PropertyException.UndefinedVariableException) e;
if (_names.length > 1)
uve.setMessage(
"Attempted to reference a property or method of an undefined variable: $" + _names[0]);
else
uve.setMessage(
"Attempted to evaluate an undefined variable: $" + _names[0]);
}
context.getEvaluationExceptionHandler()
.evaluate(this, context, e);
return null;
}
catch (Exception e)
{
// May throw
context.getEvaluationExceptionHandler()
.evaluate(this, context,
new PropertyException("Variable: exception evaluating "
+ getVariableName(), e));
return null;
}
}
/**
* Look in the hashtable (context) for a value keyed to this variables
* name and write its value to the stream.
* @exception PropertyException is required data is missing
* @exception IOException if could not write to output stream
*/
final public void write (FastWriter out, Context context)
throws PropertyException, IOException
{
try
{
Object val = getValue(context);
if (val instanceof Macro)
((Macro) val).write(out, context);
else
{
if (val != null)
{
String v = val.toString();
if (v != null)
out.write(v);
else
{
out.write(context.getEvaluationExceptionHandler()
.expand(this, context,
new PropertyException.NullToStringException(getVariableName())));
}
}
else
{
if (isSimpleName())
{
// user accessed a variable that isn't in the context
// $ObjectNotInContext
out.write(context.getEvaluationExceptionHandler()
.expand(this, context,
new PropertyException.NoSuchVariableException(getVariableName())));
}
else
{
// user accessed a valid property who's value is null
out.write(context.getEvaluationExceptionHandler()
.expand(this, context,
new PropertyException.NullValueException(getVariableName())));
}
}
}
}
catch (PropertyException e)
{
if (e instanceof PropertyException.UndefinedVariableException)
{
PropertyException.UndefinedVariableException uve = (PropertyException.UndefinedVariableException) e;
if (_names.length > 1)
uve.setMessage(
"Attempted to write a property or method value of an undefined variable: $" + _names[0]);
else
uve.setMessage(
"Attempted to write an undefined variable: $" + _names[0]);
}
out.write(context.getEvaluationExceptionHandler()
.expand(this, context, e));
}
catch (Exception e)
{
// something we weren't expecting happened!
// I wonder if we would ever get here? --eric
out.write(context.getEvaluationExceptionHandler()
.expand(this, context, e));
}
}
/**
* Helper method to construct a String name from a Object[] name
*/
final static String makeName (Object[] names)
{
StringBuffer buf = new StringBuffer();
for (int i = 0; i < names.length; i++)
{
if (i != 0)
{
buf.append(".");
}
buf.append(names[i]);
}
return buf.toString();
}
/**
* The code to get the value represented by the variable from the
* supplied context.
*/
public abstract Object getValue (Context context) throws PropertyException;
/**
* The code to set the value represented by the variable in the
* supplied context.
*/
public abstract void setValue (Context c, Object v) throws PropertyException;
/**
* Return the String name of the variable prefixed with a string
* representing its type. For example local:a.b.c
*/
public abstract String toString ();
/**
* Return the canonical name for this variable
*/
public synchronized String getVariableName ()
{
if (_vname == null) {
_vname = makeName(_names).intern();
}
return _vname;
}
public void accept (TemplateVisitor v)
{
v.visitVariable(this, _names);
}
}