You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@velocity.apache.org by jv...@locus.apache.org on 2000/08/30 12:51:26 UTC
cvs commit: jakarta-velocity/src/java/org/apache/velocity/introspection ArrayIterator.java Constant.java EnumIterator.java Log.java Macro.java Named.java PropertyException.java PropertyMethod.java PropertyOperator.java PropertyReference.java RefMap.java
jvanzyl 00/08/30 03:51:25
Added: src/java/org/apache/velocity/introspection
ArrayIterator.java Constant.java EnumIterator.java
Log.java Macro.java Named.java
PropertyException.java PropertyMethod.java
PropertyOperator.java PropertyReference.java
RefMap.java
Log:
- started playing with the WM introspection engine. I removed
some references to make the build work. Hopefully later on today
Bob and I can get this puppy running!
Revision Changes Path
1.1 jakarta-velocity/src/java/org/apache/velocity/introspection/ArrayIterator.java
Index: ArrayIterator.java
===================================================================
/*
* Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
*
* This software is the confidential intellectual property of
* of Semiotek Inc.; it is copyrighted and licensed, not sold.
* You may use it under the terms of the GNU General Public License,
* version 2, as published by the Free Software Foundation. If you
* do not want to use the GPL, you may still use the software after
* purchasing a proprietary developers license from Semiotek Inc.
*
* 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 the attached License.html file for details, or contact us
* by e-mail at info@semiotek.com to get a copy.
*/
package org.apache.velocity.introspection;
import java.util.*;
/**
* This provides an iterator interface to an array
*/
final public class ArrayIterator implements Iterator
{
final Object[] a;
int pos;
/**
* Construct an iterator given an enumeration
*/
public ArrayIterator(Object[] array)
{
this.a = array;
pos = 0;
}
/**
* Return true if we have not yet reached the end of the enumeration
*/
final public boolean hasNext()
{
return (pos < a.length);
}
/**
* Advance the iterator and return the next value. Return null if we
* reach the end of the enumeration.
*/
final public Object next() throws NoSuchElementException
{
if (pos < a.length) {
return a[pos++];
} else {
throw new NoSuchElementException("Advanced beyond end of array");
}
}
/**
* Unsupported
*/
final public void remove() throws UnsupportedOperationException
{
throw new UnsupportedOperationException();
}
/**
* Test harness
*/
static public void main(String arg[]) {
try {
Iterator i = new ArrayIterator(arg);
while (i.hasNext()) {
System.out.println("item: " + i.next());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.1 jakarta-velocity/src/java/org/apache/velocity/introspection/Constant.java
Index: Constant.java
===================================================================
/*
* Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
*
* This software is the confidential intellectual property of
* of Semiotek Inc.; it is copyrighted and licensed, not sold.
* You may use it under the terms of the GNU General Public License,
* version 2, as published by the Free Software Foundation. If you
* do not want to use the GPL, you may still use the software after
* purchasing a proprietary developers license from Semiotek Inc.
*
* 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 the attached License.html file for details, or contact us
* by e-mail at info@semiotek.com to get a copy.
*/
package org.apache.velocity.introspection;
/**
* This class allows you to use an object to represent a constant. The
* default implementation allows the object to have both a String
* and an integer representation. All subclasses must have both
* String and integer representations as well--the integer value
* is used to order the constants.
* <p>
* Constants are immutable, and usually declared final.
*/
final public class Constant
{
private String name;
private int order;
public Constant(String name, int ord)
{
this.name = name;
this.order = ord;
}
/**
* Return the declared String name for this constant
*/
final public String toString() {
return name;
}
/**
* Same as toString()
*/
final public String getName() {
return name;
}
/**
* Return the integer (order) value of this constant
*/
final public int getOrder() {
return order;
}
}
1.1 jakarta-velocity/src/java/org/apache/velocity/introspection/EnumIterator.java
Index: EnumIterator.java
===================================================================
/*
* Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
*
* This software is the confidential intellectual property of
* of Semiotek Inc.; it is copyrighted and licensed, not sold.
* You may use it under the terms of the GNU General Public License,
* version 2, as published by the Free Software Foundation. If you
* do not want to use the GPL, you may still use the software after
* purchasing a proprietary developers license from Semiotek Inc.
*
* 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 the attached License.html file for details, or contact us
* by e-mail at info@semiotek.com to get a copy.
*/
package org.apache.velocity.introspection;
import java.util.*;
/**
* Allow a Java 1.1 enumeration to be used as a JDK 1.2 style Iterator
*/
final public class EnumIterator implements Iterator
{
final Enumeration enum;
private boolean hasNext;
/**
* Construct an iterator given an enumeration
*/
public EnumIterator(Enumeration e)
{
enum = e;
hasNext = e.hasMoreElements();
}
/**
* Return true if we have not yet reached the end of the enumeration
*/
final public boolean hasNext()
{
return hasNext;
}
/**
* Advance the iterator and return the next value. Return null if we
* reach the end of the enumeration.
*/
final public Object next() throws NoSuchElementException
{
if (!hasNext) {
throw new NoSuchElementException("advanced past end of list");
}
Object o = enum.nextElement();
hasNext = enum.hasMoreElements();
return o;
}
/**
* Unsupported
*/
final public void remove() throws UnsupportedOperationException
{
throw new UnsupportedOperationException();
}
/**
* Test harness
*/
static public void main(String arg[]) {
java.util.Vector v = new java.util.Vector(arg.length);
for (int i = 0; i < arg.length; i++) {
v.addElement(arg[i]);
}
try {
Iterator i = new EnumIterator(v.elements());
while (i.hasNext()) {
System.out.println("item: " + i.next());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.1 jakarta-velocity/src/java/org/apache/velocity/introspection/Log.java
Index: Log.java
===================================================================
/*
* Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
*
* This software is the confidential intellectual property of
* of Semiotek Inc.; it is copyrighted and licensed, not sold.
* You may use it under the terms of the GNU General Public License,
* version 2, as published by the Free Software Foundation. If you
* do not want to use the GPL, you may still use the software after
* purchasing a proprietary developers license from Semiotek Inc.
*
* 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 the attached License.html file for details, or contact us
* by e-mail at info@semiotek.com to get a copy.
*/
package org.apache.velocity.introspection;
import java.io.*;
import java.util.*;
import java.text.*;
/**
* The Semiotek Log interface is intended to make logging as simple as
* possible in order to encourage its use throughout the system. Log
* messages are categorized two ways : what type/level they are,
* and what part of the system they come from. The type of the log
* level ranges from "error" (most severe) to "debug" (least severe).
* The part of the system is represented by the "class" of the log
* message--this is an arbitrary string.
* <P>
* The Log class is divided into static methods which are used to control
* the overall behavior of the logging system, and instance methods which
* are used to submit log messages to the log.
* <P>
* The expected way to use this class is to create a Log object in a
* package called "log" and then call it from various points in that
* package to generate log messages. The "log" object should not be
* visible beyond the scope of its intended callers.
*
*/
final public class Log
{
// static portion relates to overall control & log behavior
// STATIC VARIABLES
/**
* Use this variable in your code to decide whether or not to include
* debug() log messages. A good technique is to use a local variable in
* your class like this:
*<blockquote>
* final boolean debug = false && Log.debug
*</blockquote>
* @see #debug()
*/
public static final boolean debug = true;
/**
* All the current log object names and types
*/
final private static Vector logsRegistered = new Vector();
/**
* The log types that are selected for output. null means all.
*/
private static Hashtable typesEnabled = null;
/**
* setLevel(Log.NONE) turns off all log messages
*/
public static final Constant NONE = new Constant("NONE",0);
/**
* use with setLevel() to turn off all log messages except ERROR
* @see #error()
*/
public static final Constant ERROR = new Constant("ERROR",1);
/**
* use with setLevel() to turn off all log messages except ERROR/WARNING
* @see #warning()
*/
public static final Constant WARNING = new Constant("WARNING",2);
/**
* use with setLevel() to turn off messages other than ERROR/WARNING/INFO
* @see #info()
*/
public static final Constant INFO = new Constant("INFO",3);
/**
* use with setLevel() to turn off all log messages except
* ERROR/WARNING/INFO/EXCEPTION
* @see #exception()
*/
public static final Constant EXCEPTION = new Constant("EXCEPTION",4);
/**
* use with setLevel() to turn off all log messages except
* ERROR/WARNING/INFO/EXCEPTION/DEBUG
* @see #debug()
*/
public static final Constant DEBUG = new Constant("DEBUG",5);
/**
* use with setLevel() to turn on all log messages
*/
public static final Constant ALL = new Constant("ALL",6);
/**
* Used to flag which types are enabled
*/
private static final String TYPE_ENABLED = "ENABLED";
/**
* system independent line separator
*/
private final static String NEWLINE = System.getProperty("line.separator");
/**
* Current level of log messages being printed, defaults to INFO
*/
static private int myLevel = INFO.getOrder();
/**
* Current target for the log
*/
static private PrintWriter myTarget = new PrintWriter(System.out,true);
/**
* Whether or not we should be stack tracing exceptions
*/
static private boolean iTraceExceptions = false;
/**
* How timestamps in the log are written out
*/
static private DateFormat dateFmt
= DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.SHORT);
// STATIC METHODS
/**
* Get the log constant that corresponds to the supplied integer. If
* the integer is out of range the closest constant will be returned.
*/
static final public Constant getConstant(int i) {
Constant ret;
switch(i) {
case 0: ret = NONE; break;
case 1: ret = ERROR; break;
case 2: ret = WARNING; break;
case 3: ret = INFO; break;
case 4: ret = EXCEPTION; break;
case 5: ret = DEBUG; break;
case 6: ret = ALL; break;
default:
if (i < 0) {
ret = NONE;
} else {
ret = ALL;
}
break;
}
return ret;
}
/**
* Find the constant that matches the supplied string by name. Case
* is ignored. If the supplied name does not match any constants, then
* the constant ALL is returned.
*/
static final public Constant getConstant(String name) {
Constant ret;
if (name.equalsIgnoreCase(NONE.getName())) { ret = NONE; }
else if (name.equalsIgnoreCase(ERROR.getName())) { ret = ERROR; }
else if (name.equalsIgnoreCase(WARNING.getName())) { ret = WARNING; }
else if (name.equalsIgnoreCase(INFO.getName())) { ret = INFO; }
else if (name.equalsIgnoreCase(EXCEPTION.getName())) { ret = EXCEPTION; }
else if (name.equalsIgnoreCase(DEBUG.getName())) { ret = DEBUG; }
else { ret = ALL; }
return ret;
}
/**
* Log level: what messages to we print out?
* @param logLevelConstant use one of the log constants defined above
*/
static final public void setLevel(Constant logLevel) {
myLevel = logLevel.getOrder();
myTarget.println("*** LOG LEVEL SET TO: " + logLevel);
}
/**
* Log target: where do we write log messages?
* @param target the PrintWriter you want to send log messages to
*/
static final public void setTarget(PrintWriter target) {
if (target == null) {
target = new PrintWriter(new OutputStreamWriter(System.err));
}
myTarget = target;
myTarget.println("*** BEGIN: " + dateFmt.format(new Date()) + "***");
}
/**
* Log target: where do we write log messages?
* @param target the OutputStream you want to send log messages to
*/
static final public void setTarget(OutputStream target) {
setTarget( (target == null) ?
null : new PrintWriter(new OutputStreamWriter(target)));
}
/**
* Log target: set the log to be this file
* @param logfile the name of a file you want to log messages to
* @throws IOException if the file could not be opened
*/
static final public void setTarget(String logfile) throws IOException
{
PrintWriter out;
if (logfile != null) {
out = new PrintWriter(new FileWriter(logfile,true));
} else {
out = new PrintWriter(new OutputStreamWriter(System.err));
}
setTarget(out);
out.flush();
}
/**
* Log includes exception stacktraces
* @param trace true if you want exception stacktraces, false otherwise
*/
static final public void traceExceptions(boolean trace) {
iTraceExceptions = trace;
}
/**
* Private utility method for writing the log (do not add newline)
*/
static final private void write(String level, String type, Object message)
{
if ((typesEnabled != null) && typesEnabled.get(type) != TYPE_ENABLED) {
return;
}
try {
myTarget.print(dateFmt.format(new Date())
+ "\t" + type
+ "\t" + level
+ "\t" + message);
myTarget.flush();
} catch (java.lang.Exception e) {
System.err.println("** COULD NOT WRITE LOG! SWITCHING TO STDERR **");
System.err.println(dateFmt.format(new Date())
+ "\t" + level + "\t" + message);
System.err.flush();
setTarget(new PrintWriter(System.err));
}
}
/**
* Private utility method for writing the log (add newline)
*/
static final private void writeln(String level, String type, Object message)
{
write(level,type,message + NEWLINE);
}
/**
* Return an enumeration containing a list of the registered log types
*/
static final public Enumeration getTypes() {
return logsRegistered.elements();
}
/**
* Return an enumeration of the logs types that are currently enabled.
*/
static final public Enumeration getTypesEnabled() {
if (typesEnabled == null) {
return logsRegistered.elements();
} else {
return typesEnabled.keys();
}
}
/**
* Allow printing of only the supplied log types
*/
static final public void enableTypes(String[] types)
{
disableAllTypes();
for (int i = 0; i < types.length; i++) {
enableType(types[i]);
}
}
/**
* Allow printing of the supplied type. If all types are currently
* allowed (via enableAllTypes) then this does nothing; if all types
* are currently disallowed (via disableAllTypes) then this re-enables
* just this one type. You can call it repeatedly to re-enable
* several different types.
*/
static public final void enableType(String type) {
Hashtable types = typesEnabled;
if (typesEnabled == null) {
return; // all enabled already
} else {
types.put(type,TYPE_ENABLED);
}
}
/**
* Allow printing of all types.
*/
static public final void enableAllTypes() {
typesEnabled = null;
}
/**
* Disallow printing of all types. You probably want to call
* enableType() a few times after this.
*/
static public final void disableAllTypes() {
typesEnabled = new Hashtable();
}
// INSTANCE VARIABLES
/**
* The type name for this log type, should be fairly short
*/
private String logType;
/**
* A description of what this log type is for
*/
private String logDescr;
// INSTANCE METHODS
/**
* Create a new log in the specified class. It is recomended that
* the type paramter be a word of seven or fewer characters in order
* to produce a consistent looking log.
* <P>
* @param type a single word representing package/category
* @description a brief explanation of what this category means
*/
public Log(String type, String description)
{
this.logType = type.intern(); // allow "==" tests for equality
this.logDescr = description;
logsRegistered.addElement(this);
}
/**
* Use to write a log message that indicates a programming error
* or a system level misconfiguration: for example, unable to
* locate the config file, etc. Error should not be used
* to indicate an error in data being processed, but rather an
* error condition that is the fault of the program itself.
*/
final public void error(Object logMessage) {
if (myLevel >= ERROR.getOrder()) {
writeln("ERROR", logType, logMessage);
}
}
/**
* Use to write a log message that indicats suspicious but
* non-fatal behavior in the program; or else which represents a
* fatal error that is the fault of data passed to the program.
*/
final public void warning(Object logMessage) {
if (myLevel >= WARNING.getOrder()) {
writeln("WARN", logType, logMessage);
}
}
/**
* Write a log message that simply informs of some interesting
* events, such as program start up or shut down.
*/
final public void info(Object logMessage) {
if (myLevel >= INFO.getOrder()) {
writeln("INFO", logType, logMessage);
}
}
/**
* Write a log message that indicates an exceptional condition
* has occurred. It is normal to pass an actual exception in as
* the object to be logged--if the object passed is actually an
* exception and stack tracing is enabled, this message will then
* be able to generate the appropraite stack trace for the Log.
*/
final public void exception(Object logMessage) {
if (myLevel < EXCEPTION.getOrder()) {
return;
}
if (iTraceExceptions && logMessage instanceof Exception) {
Exception e = (Exception) logMessage;
write("EXCPT", logType, "");
e.printStackTrace(myTarget);
myTarget.flush();
} else {
writeln("EXCPT", logType, logMessage);
}
}
/**
* Use to write debugging information to the log. This is information
* that would normally only be of interest to someone trying to
* discover the actual behavior of the program at runtime.
* <p>
* It is normal to wrap a Log.debug() like this:
* <blockquote><pre>
* if (Log.debug) { log.debug("debug msg"); }
* </pre></blockquote>
* The "debug" boolean in the Log class can then be toggled on or
* off in the source, thereby allowing debug statements to be optimized
* out of the source code at compile time.
*/
final public void debug(Object logMessage) {
if (myLevel >= DEBUG.getOrder()) {
writeln("DEBUG", logType, logMessage);
}
}
// TEST HARNESS
/**
* Test harness: opens "log" in the current directory
*/
public static void main(String arg[]) {
try {
System.out.println("Logging to \"log\" in the current directory.");
Log.setTarget("log");
Log log = new Log("testing", "just used for testing");
for (int i = NONE.getOrder(); i <= ALL.getOrder(); i++) {
log.setLevel(getConstant(i));
log.error("Testing error");
log.warning("This is a warning");
log.info("This is informative.");
log.exception("An exception");
log.debug("Debug junk");
}
} catch (java.lang.Exception e) {
e.printStackTrace();
}
}
}
1.1 jakarta-velocity/src/java/org/apache/velocity/introspection/Macro.java
Index: Macro.java
===================================================================
/*
* Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
*
* This software is the confidential intellectual property of
* of Semiotek Inc.; it is copyrighted and licensed, not sold.
* You may use it under the terms of the GNU General Public License,
* version 2, as published by the Free Software Foundation. If you
* do not want to use the GPL, you may still use the software after
* purchasing a proprietary developers license from Semiotek Inc.
*
* 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 the attached License.html file for details, or contact us
* by e-mail at info@semiotek.com to get a copy.
*/
package org.apache.velocity.introspection;
import java.util.*;
import java.io.*;
import org.apache.velocity.Context;
/**
* Directives, variables, macro calls, blocks, conditions, text, etc., all
* have this as their supertype.
*/
public interface Macro extends PropertyReference
{
/**
* Interpret the directive and write it out, using the values in
* the supplied context as appropriate.
* <p>
* @exception ContextException if required data was missing from context
* @exception IOException if we could not successfully write to out
*/
//public void write(FastWriter out, Context context)
// throws ContextException, IOException;
/**
* same as out but returns a String
* <p>
* @exception ContextException if required data was missing from context
*/
public Object evaluate(Context context);
//throws ContextException;
}
1.1 jakarta-velocity/src/java/org/apache/velocity/introspection/Named.java
Index: Named.java
===================================================================
package org.apache.velocity.introspection;
public interface Named
{
public String getName();
}
1.1 jakarta-velocity/src/java/org/apache/velocity/introspection/PropertyException.java
Index: PropertyException.java
===================================================================
/*
* Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
*
* This software is the confidential intellectual property of
* of Semiotek Inc.; it is copyrighted and licensed, not sold.
* You may use it under the terms of the GNU General Public License,
* version 2, as published by the Free Software Foundation. If you
* do not want to use the GPL, you may still use the software after
* purchasing a proprietary developers license from Semiotek Inc.
*
* 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 the attached License.html file for details, or contact us
* by e-mail at info@semiotek.com to get a copy.
*/
package org.apache.velocity.introspection;
/**
* This exception is raised by the Property class when, for some reason,
* an unrecoverable error occurs while attempting to access the declared
* property name from the parent object. Possible reasons are: security
* violation, the property doesn't exist, or a supplied argument is of
* the wrong type.
*/
final public class PropertyException extends Exception
{
final Throwable t;
public PropertyException(String reason, Throwable t)
{
super(reason);
this.t = t;
}
/**
* You can query a PropertyException to find out what the
* underlying exception was, if there was one.
* <p>
* @returns null if the property was simply not found
*/
public Throwable getThrowable() {
return t;
}
}
1.1 jakarta-velocity/src/java/org/apache/velocity/introspection/PropertyMethod.java
Index: PropertyMethod.java
===================================================================
/*
* Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
*
* This software is the confidential intellectual property of
* of Semiotek Inc.; it is copyrighted and licensed, not sold.
* You may use it under the terms of the GNU General Public License,
* version 2, as published by the Free Software Foundation. If you
* do not want to use the GPL, you may still use the software after
* purchasing a proprietary developers license from Semiotek Inc.
*
* 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 the attached License.html file for details, or contact us
* by e-mail at info@semiotek.com to get a copy.
*/
package org.apache.velocity.introspection;
import org.apache.velocity.Context;
//import org.webmacro.*;
/**
* A property method can function as part of a name in a
* property, and represents a method call that should be used
* to resolve that portion of the name.
* <p>
* For example: a.b.get("C").d is equivalent to a.b.hi.d, you can
* also use this to access methods which are not normally available
* through the regular introspection algorithms, for example:
* #set $thing = a.get("thing")
* <p>
* The arguments supplied to a PropertyMethod can be a list
* including Macro objects which need to be resolved
* against a context. The introspection process will supply the
* context and resolve these references at execution time.
*/
final public class PropertyMethod implements Named
{
private Object _args;
private String _name;
private boolean _reference;
/**
* Create a new PropertyMethod
* @param name the name of the method to call
* @param args the arguments, including Macro objects
*/
public PropertyMethod(String name, Object[] args)
{
_name = name;
_args = args;
_reference = false;
}
/**
* Create a new PropertyMethod
* @param name the name of the method to call
* @param args the arguments, including Macro objects
*/
public PropertyMethod(String name, Macro args)
{
_name = name;
_args = args;
_reference = true;
}
/**
* Return the name of this PropertyMethod
*/
final public String getName() {
return _name;
}
/**
* Return a signature of this method
*/
final public String toString() {
if (_reference) {
return _name +_args.toString();
}
Object[] argList = (Object[]) _args;
StringBuffer vname = new StringBuffer();
vname.append(_name);
vname.append("(");
for (int i = 0; i < argList.length; i++) {
if (i != 0) {
vname.append(",");
}
vname.append(argList[i]);
}
vname.append(")");
return vname.toString();
}
/**
* Return the arguments for this method, after resolving them
* against the supplied context. Any arguments which are of
* type Macro will be resolved into a regular
* object via the Macro.evaluate method.
* @exception ContextException a Macro in the arguments failed to resolve against the supplied context
*/
final public Object[] getArguments(Context context)
//throws ContextException
{
Object[] argList;
if (_reference) {
argList = (Object[]) ((Macro) _args).evaluate(context);
} else {
argList = (Object[]) _args;
}
Object ret[] = new Object[ argList.length ];
System.arraycopy(argList,0,ret,0,argList.length);
for (int i = 0; i < ret.length; i++) {
while (ret[i] instanceof Macro) {
Object repl = ((Macro) ret[i]).evaluate(context);
if (repl == ret[i]) {
break; // avoid infinite loop
}
ret[i] = repl;
}
}
return ret;
}
}
1.1 jakarta-velocity/src/java/org/apache/velocity/introspection/PropertyOperator.java
Index: PropertyOperator.java
===================================================================
/*
* Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
*
* This software is the confidential intellectual property of
* of Semiotek Inc.; it is copyrighted and licensed, not sold.
* You may use it under the terms of the GNU General Public License,
* version 2, as published by the Free Software Foundation. If you
* do not want to use the GPL, you may still use the software after
* purchasing a proprietary developers license from Semiotek Inc.
*
* 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 the attached License.html file for details, or contact us
* by e-mail at info@semiotek.com to get a copy.
*/
package org.apache.velocity.introspection;
import java.util.*;
import java.lang.reflect.*;
import org.apache.velocity.*;
//import org.webmacro.*;
/**
* This class knows how to extract properties from objects efficiently.
* <p>
* A simple property "Bar" can be accessed in a sub-object "Foo" (and
* in one case, sub-object "Baz") using any of the following method
* signatures, listed in the order that they will be tried:
* <ul>
* <li>Foo.Bar
* <li>Foo.getBar() / Foo.setBar(object)
* <li>Foo.getBar("Baz") / Foo.setBar("Baz", object)
* <li>Foo.get("Bar") / Foo.put("Bar",object)
* </ul>
* <p>
* The PropertyOperator is capable of extracting an iterator from an object
* if in any of the following conditions are true, listed in the order that
* they will be tried:
* <ul>
* <li>The object itself is an array
* <li>The object itself is an Iterator
* <li>The object itself is an Enumeration
* <li>The object has an "Iterator iterator()" method
* <li>The object has an "Enumeration elements()" method
* </ul>
* <p>
* You can specify a long list of property names, and the above methods
* will be recursively applied. For example, if your list were
* Order,Customer,Fred,Name,Last the PropertyOperator might be
* able to access it as:<pre>
* Order.getCustomer("Fred").getName().Last
* </pre>
*/
final public class PropertyOperator
{
// debugging
static final boolean _debug = Log.debug && false;
static final Log _log = new Log("prop","Property Introspection");
// static public interface
/**
* Attempt to retrieve a property using the rules of property
* introspection described above. Begin reading names at position
* start in the array of names.
* @param context is used to resolve sub-properties in arguments
* @param instance is the root of introspection
* @param names property names, one per array entry
* @return the property described by the names, inside the instance
* @exception PropertyException the property we'd like to look at
* @exception SecurityExeption you are not permitted to try
* @exception ContextException one of the names is a PropertyReference which could not be evaluated against the supplied context
*/
static final public Object getProperty(
final Context context, final Object instance,
final Object[] names, int start)
throws PropertyException, SecurityException //, ContextException
{
try {
if (instance == null) {
return null;
} else {
return getOperator(instance.getClass()).getProperty(
context,instance,names,start,names.length - 1);
}
} catch (NoSuchMethodException e) {
_log.exception(e);
throw new PropertyException("No method to access property: " + e,e);
}
}
/**
* Calls getProperty(context, instance, names, 0)
*/
static final public Object getProperty(
final Context context, final Object instance, final Object[] names)
throws PropertyException, SecurityException //, ContextException
{
return getProperty(context, instance, names, 0);
}
/**
* Given a property description name, attempt to set the property
* value to the supplied object.
* @param context An object containing a property
* @param names The string names of that property
* @param value the new value the property is to be set to
* @exception PropertyException not possible to set the property
* @exception SecurityException you are not permitted to try
* @exception ContextException a PropertyReference in the argument names could not be resolved against the supplied context
*/
static final public boolean setProperty(
final Context context, Object instance,
final Object[] names, int start, final Object value)
throws PropertyException, SecurityException //, ContextException
{
try {
if (instance == null) {
return false;
}
return getOperator(instance.getClass()).setProperty(context,instance,names,value,start);
} catch (NoSuchMethodException e) {
throw new PropertyException("No method to access property: " + e,e);
}
}
/**
* Calls setProperty(context, names, 0, value)
*/
static final public boolean setProperty(
final Context context, final Object instance,
final Object[] names, final Object value)
throws PropertyException, SecurityException //, ContextException
{
return setProperty(context,instance,names,0,value);
}
/**
* Evaluate the supplied object and work out a way to return it
* as an iterator.
* @param context an object believed to represent a list
* @return an Iterator that iterates through that list
* @exception PropertyException could not extract iterator from instance
*/
static final public Iterator getIterator(Object instance)
throws PropertyException
{
if (instance instanceof Object[]) {
return new ArrayIterator((Object[])instance);
} else if (instance instanceof Iterator) {
return (Iterator) instance;
} else if (instance instanceof Enumeration) {
return new EnumIterator((Enumeration)instance);
} else {
return getOperator(instance.getClass()).findIterator(instance);
}
}
// operator cache
/**
* Private cache of all the property operators constructed so far
*/
static private RefMap _operators = new RefMap();
/**
* Find the PropertyOperator that knows about this type of object
*/
static private PropertyOperator getOperator(final Class type)
throws PropertyException
{
PropertyOperator o = (PropertyOperator) _operators.get(type);
if (o == null) {
synchronized (_operators) {
// if ((_operators.size() * 2) > _operators.capacity()) {
// _operators = _operators.copy(_operators.capacity() * 2 + 1);
// }
try {
o = new PropertyOperator(type);
} catch (Exception e) {
e.printStackTrace();
}
_operators.put(type,o);
}
}
return o;
}
/**
* My accessors for fields, and binary methods
*/
final private HashMap _unaryAccessors = new HashMap();
/**
* Accessors that require an additional property name
*/
final private HashMap _binaryAccessors = new HashMap();
/**
* Accessors for direct method calls
*/
final private HashMap _directAccessors = new HashMap();
/**
* Hash table accessor
*/
private BinaryMethodAccessor _hashAccessor;
/**
* The iterator method we found
*/
private Method iteratorMethod = null;
/**
* Get the public methods for the named class. The vector meths
* will be populated by a list of all the methods. Note that a method
* may appear more than once in the vector if it is declared in more
* than one superclass or interface.
*/
private void getAllMethods(HashMap meths, Class c)
throws SecurityException
{
if (Modifier.isPublic( c.getModifiers() ) ) {
Method m[] = c.getDeclaredMethods();
for (int i = 0; i < m.length; i++) {
if ( Modifier.isPublic( m[i].getModifiers() ) ) {
addMethod(meths, m[i]);
}
}
}
Class iface[] = c.getInterfaces();
for (int i = 0; i < iface.length; i++) {
getAllMethods(meths, iface[i]);
}
Class sup = c.getSuperclass();
if (sup != null) {
getAllMethods(meths, sup);
}
}
/**
* The lhs precedes the rhs if it has fewer parameters. If the lhs
* has the same number of parameters then the lhs precedes the rhs
* if it can be used anywhere the rhs can be used--meaning that for
* each and every term, the lhs is the same or more specific than
* the rhs. If they have the same number of parameters but are not
* related at all, put the lhs later.
*/
private int precedes(Class[] lhs, Class[] rhs)
{
if (lhs.length == rhs.length) {
for (int i = 0; i < lhs.length; i++) {
if (! rhs[i].equals(lhs[i])) {
if (lhs[i].isAssignableFrom(rhs[i])) {
// rhs is more specific than lhs
return 1;
}
if (rhs[i].isAssignableFrom(lhs[i])) {
// lhs is more specific than rhs
return -1;
}
// not related by inheritance, put lhs later on
return 1;
}
}
return 0; // all the same
} else {
return (lhs.length < rhs.length) ? -1 : 1;
}
}
private void addMethod(HashMap hm, Method m) {
String name = m.getName();
Object o = hm.get( name );
if (o == null) {
hm.put(name, m);
return;
}
Vector v;
if (o instanceof Method) {
v = new Vector();
v.addElement(o);
hm.put(name, v);
} else {
v = (Vector) o;
}
Class ptypes[] = m.getParameterTypes();
for (int i = 0; i < v.size(); i++) {
Class curTypes[] = ((Method) v.elementAt(i)).getParameterTypes();
int order = precedes(ptypes, curTypes);
if (order < 0) {
v.insertElementAt(m,i);
return;
} else if (order == 0) {
// ignore duplicate method
return;
}
}
v.addElement(m);
}
/**
* Get all the public methods of the supplied class. They will be
* returned in arbitrary alphabetical order, but where there are
* multiple methods with the same name, they will be returned in
* order of precedence: least arguments first, and most specific
* arguments before less specific arguments. See precedes().
*/
private Vector getMethods(Class c) {
Vector v = new Vector();
HashMap h = new HashMap();
getAllMethods(h,c);
Iterator iter = h.values().iterator();
while (iter.hasNext()) {
Object elem = iter.next();
if (elem instanceof Method) {
v.addElement( elem );
} else {
Vector v1 = (Vector) elem;
for (int i = 0; i < v1.size(); i++) {
v.addElement( v1.elementAt(i) );
}
}
}
return v;
}
/**
* Construct a property operator for the target class
*/
private PropertyOperator(final Class target)
throws SecurityException, PropertyException
{
if (_debug) {
_log.debug("new PropertyOperator(" + target + ")");
}
Accessor acc;
// introspect fields first
Field[] fields = target.getFields();
for (int i = 0; i < fields.length; i++) {
if (Modifier.isPublic(fields[i].getModifiers())) {
if (_debug) {
_log.debug("Adding field: " + fields[i]);
}
acc = new FieldAccessor(fields[i]);
_unaryAccessors.put(acc.getName(),acc);
} else if (_debug) {
_log.debug("Skipped non-public field: " + fields[i]);
}
}
// introspect methods second
Vector methods = getMethods(target);
Method meth;
Class[] params;
String name,propName;
for (int i = 0; i < methods.size(); i++)
{
meth = ((Method) methods.elementAt(i));
name = meth.getName();
params = meth.getParameterTypes();
int plength = params.length;
// add direct accessor
acc = (Accessor) _directAccessors.get(name);
if (acc != null) {
((DirectAccessor) acc).addMethod(meth,params);
} else {
acc = new DirectAccessor(name,meth,params);
_directAccessors.put(name,acc);
}
// check for get/set/put method
if ((name.startsWith("get") ||
name.startsWith("set")) || name.equals("put"))
{
propName = name.substring(3);
if ( ((plength == 0) && name.startsWith("get")) ||
((plength == 1) && name.startsWith("set")) )
{
// unary get/set method
acc = (Accessor) _unaryAccessors.get(propName);
if (acc != null) {
if (acc instanceof MethodAccessor) {
if (_debug) {
_log.debug("Updating existing accessor: " + meth);
}
((MethodAccessor) acc).addMethod(meth,params);
} else if (_debug) {
_log.debug("Superceded by a field: " + meth);
}
} else {
if (_debug) {
_log.debug("Adding new accessor: " + meth);
}
acc = new UnaryMethodAccessor(propName,meth,params);
_unaryAccessors.put(propName,acc);
}
} else if ( (plength > 0) && (
(params[0].isInstance("string") &&
((plength == 2) && name.equals("put"))) ||
((plength == 1) && name.equals("get"))))
{
// hashtable get/put
if (_hashAccessor != null) {
if (_debug) {
_log.debug("Updating hash accessor: " + meth);
}
_hashAccessor.addMethod(meth,params);
} else {
if (_debug) {
_log.debug("Creating a new hash accessor: " + meth);
}
_hashAccessor = new BinaryMethodAccessor(propName,meth,params);
}
} else if ((plength > 0) && (params[0].isInstance("string")) &&
(((plength == 1) && name.startsWith("get")) ||
((plength == 2) && name.startsWith("set"))))
{
// binary get/set method
acc = (Accessor) _binaryAccessors.get(propName);
if (acc != null) {
if (_debug) {
_log.debug("Updating binary accessor: " + meth);
}
((MethodAccessor) acc).addMethod(meth,params);
} else {
if (_debug) {
_log.debug("Creating a new binary accessor: " + meth);
}
acc = new BinaryMethodAccessor(propName,meth,params);
_binaryAccessors.put(propName,acc);
}
}
} else if (name.equals("elements") ||
name.equals("enumeration") ||
name.equals("iterator") ||
name.equals("toArray"))
{
if (params.length == 0) {
Class returnType = meth.getReturnType();
// iterator supercedes enumeration supercedes Object[]
Class iterClass = Iterator.class;
boolean iterA = iterClass.isAssignableFrom(returnType);
if (
iterA ||
(((iteratorMethod == null) ||
iteratorMethod.getName().equals("toArray")) &&
Object[].class.isAssignableFrom(returnType) ||
Enumeration.class.isAssignableFrom(returnType)))
{
if (_debug) {
_log.debug("Setting iterator method: " + meth);
}
iteratorMethod = meth;
}
}
}
}
}
/**
* Locate the requested property as follows: beginning from instance,
* look at names[start] and resolve it, and continue resolving names
* recursively until names[end] has been resolved. Return that.
* @param instance object to start from
* @param names the property names we are searching for
* @param start which name to look for first
* @param end which name to look for lst
* @exception PropertyException error resolving a name
* @exception NoSuchMethodException no method available for name
* @return the property requested
*
*/
private Object getProperty(
final Context context, final Object instance, final Object[] names,
int start, int end)
throws PropertyException, NoSuchMethodException //, ContextException
{
if (_debug) {
_log.debug("getProperty(" + instance + "," + names[start] + "..."
+ names[end] + "," + start + "," + end + ")");
}
String prop;
Object nextProp = null;
Accessor acc = null;
if (names[start] instanceof String) {
prop = (String) names[start];
} else if (names[start] instanceof PropertyMethod) {
PropertyMethod pm = (PropertyMethod) names[start];
prop = pm.getName();
acc = (Accessor) _directAccessors.get(prop);
Object[] args = pm.getArguments(context);
try {
nextProp = acc.get(instance,args);
start++;
} catch (NoSuchMethodException e) {
throw new NoSuchMethodException("No method " + pm + " on object " + instance);
}
} else {
prop = names[start].toString();
}
// unary?
if (acc == null) {
acc = (Accessor) _unaryAccessors.get(prop);
if (acc != null) {
if (_debug) {
_log.debug("Trying unary accesor: " + acc);
}
try {
nextProp = acc.get(instance);
start++;
} catch (NoSuchMethodException e) {
if (_debug) {
_log.debug("No suitable unary get in " + acc);
}
acc = null;
}
}
}
// binary?
if (acc == null) {
acc = (Accessor) _binaryAccessors.get(prop);
if ((acc != null) && ( (start+1) <= end) ) {
if (_debug) {
_log.debug("Trying binary accesor: " + acc);
}
try {
nextProp = acc.get(instance, (String) names[start + 1]);
start += 2;
} catch (NoSuchMethodException e) {
if (_debug) {
_log.debug("No suitable binary get in " + acc);
}
acc = null;
} catch (ClassCastException e) {
// names[start + 1] was not a String, just move on
// this catch is more efficient than using instanceof
// since 90% of the time it really will be a string
acc = null;
}
} else {
acc = null;
}
}
// hash?
if (acc == null) {
acc = _hashAccessor;
try {
if (acc != null) {
if (_debug) {
_log.debug("Trying hash accessor=" + acc +
" with prop=" + prop);
}
nextProp = acc.get(instance,prop);
start++;
if (_debug) {
_log.debug("Got: " + nextProp);
}
}
} catch (NoSuchMethodException e) {
if (_debug) {
_log.debug("No suitable hash get in " + acc);
}
acc = null;
}
}
if (acc == null) {
throw new NoSuchMethodException("No public method on object " +
instance + " of " + instance.getClass() +
" for property " + names[start] + "--is this the right class?");
}
if (_debug) {
_log.debug("Using accessor: " + acc);
}
if (start <= end) {
try {
return getOperator(nextProp.getClass()).getProperty(context,nextProp,names,start,end);
} catch (NullPointerException e) {
throw new PropertyException("No way to access property " +
names[start] + " on object " + instance + " of "
+ instance.getClass() + "--possibly null?",e);
}
} else {
return nextProp;
}
}
/**
* This method behaves a lot like getProperty, but it's tricker. It
* first tries to resolve the property using a direct method, then
* it falls back and tries a binary approach last. In order to do
* this it has to recurse into the direct approach, then detect if
* that failed and try the binary approach. It relies on getProperty
* for navigation, which is why getProperty takes start/end args.
* @param instance the object to start from
* @names path to a property we would like to set
* @value the value we'd like to set it to
* @pos we could set names[pos] from here
* @return true if we succeeded in setting, false otherwise
*/
private boolean setProperty(
Context context, Object instance, Object[] names, Object value, int pos)
throws PropertyException, NoSuchMethodException //, ContextException
{
if (_debug) {
_log.debug("setProperty(" + instance + "," + names[pos] + "..."
+ "," + value + "," + pos + ")");
}
// names[pos] is what we could set from here
int parentPos = names.length - 1;
int binPos = parentPos - 1;
// if we're not yet at the binary-settable parent, go there
if (pos < binPos) {
Object grandparent = getProperty(context,instance,names,pos,binPos - 1);
if (_debug) {
_log.debug("Advanced to " + names[binPos] + "=" + grandparent);
}
PropertyOperator po = getOperator(grandparent.getClass());
return po.setProperty(context,grandparent,names,value,binPos);
}
// if we're at the binary-settable parent, try direct first
if (pos == binPos) {
if (_debug) {
_log.debug("I am " + names[pos] + "(" + instance + ")" +
" could be binary, but first recurse to try unary access for " + names[pos]);
}
// try direct -- move to direct parent and try from there
Object parent = null;
try {
parent = getProperty(context,instance,names,pos,pos);
if (parent != null) {
PropertyOperator po = getOperator(parent.getClass());
if (po.setProperty(context,parent,names,value,pos+1)) {
return true;
}
}
} catch (NoSuchMethodException e) {
// oh well, keep trying: XXX this makes binOp expensive
}
// if direct failed, try binary
if (_debug) {
_log.debug("I am " + names[pos] + "(" + instance + ")" +
"direct failed, try bin for " + names[pos] + "." + names[pos + 1]);
}
Accessor binOp = (Accessor) _binaryAccessors.get(names[pos]);
if (binOp != null) {
if (_debug) {
_log.debug("Found binOp accessor: " + binOp);
}
try {
return binOp.set(instance,(String) names[pos+1],value);
} catch (ClassCastException e) {
if (_debug) {
_log.debug("Binary Failed because " + names[pos + 1] + " was not a string; it is " + names[pos + 1].getClass());
}
// names[pos+1] was not a string, just move on
return false;
} catch (NoSuchMethodException e) {
if (_debug) {
_log.debug("Binary Failed: " + e);
}
return false;
}
} else {
if (_debug) {
_log.debug("No binary accessor for " + names[pos]);
}
}
return false;
}
// we're the direct parent, use unaryOp or hash method
if (_debug) {
_log.debug("I am " + names[pos] + "(" + instance + ")"
+ "trying direct");
}
Accessor unaryOp = (Accessor) _unaryAccessors.get(names[pos]);
try {
if ((unaryOp != null) && unaryOp.set(instance,value)) {
return true;
}
if (_hashAccessor != null) {
return _hashAccessor.set(instance,(String) names[pos],value);
}
} catch(NoSuchMethodException e) {
// fall through
} catch(ClassCastException e) {
// names[pos] was not a string, fall through
}
return false;
}
/**
* Introspect the current object and return an Iterator representation
* of it, if possible.
* @param instance the object we think contains a list
* @exception PropertyException current object is not any sort of list
* @return an iterator representing the current object's list
*/
private Iterator findIterator(Object instance)
throws PropertyException
{
if (iteratorMethod != null) {
try {
Object ret = invoke(iteratorMethod,instance,null);
if (ret instanceof Iterator) {
return (Iterator) ret;
} else if (ret instanceof Enumeration) {
return new EnumIterator((Enumeration) ret);
} else if (ret instanceof Object[]) {
return new ArrayIterator((Object[]) ret);
}
} catch (NoSuchMethodException e) {
throw new PropertyException("Error in PropertyOperator!",e);
}
}
throw new PropertyException(instance + " is not a list",null);
}
/**
* Invoke a method on an instance, with arguments--generate
* PropertyException rather than the default Java exceptions.
* @param meth the method to invoke
* @param instance the object to invoke it on
* @param args arguments for the method
* @return return value of the method
*/
static Object invoke(Method meth, Object instance, Object[] args)
throws PropertyException, NoSuchMethodException
{
try {
return meth.invoke(instance,args);
} catch (IllegalAccessException e) {
throw new PropertyException(
"You don't have permission to access the requested method (" +
meth + " in class " + instance.getClass() +
" on object " + instance + "). Private/protected/package access " +
" values cannot be accessed via property introspection.",e);
} catch (IllegalArgumentException e) {
throw new PropertyException(
"Some kind of error occurred processing your request: this " +
"indicates a failure in PropertyOperator.java that should be " +
"reported: attempt to access method " + meth + " on object " +
instance + " with " +args.length + " parameters " +
" threw an exception: " + e,e);
} catch (InvocationTargetException e) {
throw new PropertyException(
"Attempt to invoke method " + meth + " on object "
+ instance + " of " + instance.getClass() +
" raised an exception: " + e.getTargetException(),
e.getTargetException());
} catch (NullPointerException e) {
if (meth == null) {
throw new NoSuchMethodException("Null method");
}
throw new PropertyException(
"NullPointerException thrown from method " + meth +
" on object " + instance + " -- most likely you have attempted " +
"to use an undefined value, or a failure in that method.",e);
}
}
}
// helper classes
/**
* An accessor represents one particular operation that can be
* performed on one particular class: getting/setting a field, or
* getting/setting via a method.
*/
abstract class Accessor {
private String _name;
Accessor(String name) {
_name = name;
}
final String getName() {
return _name;
}
public final String toString() {
return "Accessor:" + _name;
}
/**
* Unary get
*/
Object get(Object instance)
throws PropertyException, NoSuchMethodException
{
throw new PropertyException("BUG in PropertyOperator.java!",null);
}
/**
* Unary set
*/
boolean set(Object instance, Object value)
throws PropertyException, NoSuchMethodException
{
throw new PropertyException("BUG in PropertyOperator.java!",null);
}
/**
* Binary get
*/
Object get(Object instance, String subName)
throws PropertyException, NoSuchMethodException
{
throw new PropertyException("BUG in PropertyOperator.java!",null);
}
/**
* Binary
*/
boolean set(Object instance, String subName, Object value)
throws PropertyException, NoSuchMethodException
{
throw new PropertyException("BUG in PropertyOperator.java!",null);
}
/**
* Direct get
*/
Object get(Object instance, Object[] args)
throws PropertyException, NoSuchMethodException
{
throw new PropertyException("BUG in PropertyOperator.java!",null);
}
}
/**
* An accessor that knows how to get/set from a field
*/
final class FieldAccessor extends Accessor
{
private Field _field;
FieldAccessor(final Field f) {
super(f.getName());
_field = f;
}
final Object get(final Object instance)
throws PropertyException
{
try {
return _field.get(instance);
} catch (Exception e) {
throw new PropertyException("Unable to read field " + _field +
" on object " + instance + " of " + instance.getClass(),
e);
}
}
final boolean set(final Object instance, final Object value)
throws PropertyException
{
try {
_field.set(instance,value);
} catch (Exception e) {
throw new PropertyException("Unable to write field " + _field +
" on object " + instance + " of " + instance.getClass(),
e);
}
return true;
}
}
/**
* accessor for direct method calls, rather than property-style
*/
final class DirectAccessor extends Accessor
{
Vector _methods = new Vector();
DirectAccessor(final String name, final Method m, final Class[] params)
{
super(name);
addMethod(m,params);
}
final void addMethod(final Method m, Class[] params)
{
_methods.addElement(m);
}
final boolean matches(Class[] sig, Class[] args)
{
if (args.length != sig.length) {
return false;
}
for (int i = 0; i < sig.length; i++) {
try {
if (! sig[i].isAssignableFrom(args[i]))
{
if (sig[i].isPrimitive())
{
Class s = sig[i];
Class a = args[i];
if (
(s.equals(Integer.TYPE) && a.equals(Integer.class)) ||
(s.equals(Boolean.TYPE) && a.equals(Boolean.class)) ||
(s.equals(Character.TYPE) && a.equals(Character.class)) ||
(s.equals(Long.TYPE) && a.equals(Long.class)) ||
(s.equals(Short.TYPE) && a.equals(Short.class)) ||
(s.equals(Double.TYPE) && a.equals(Double.class)) ||
(s.equals(Float.TYPE) && a.equals(Float.class)) ||
(s.equals(Void.TYPE) && a.equals(Void.class)) ||
(s.equals(Byte.TYPE) && a.equals(Byte.class))
)
{
continue;
}
}
if ( PropertyOperator._debug)
{
PropertyOperator._log.debug("method " + this + " failed to match because " + sig[i] + " required but got " + args[i]);
}
return false;
}
} catch (NullPointerException e) {
return false; // XXX: block nulls, isAssign... throws this
}
}
return true;
}
final Object get(Object instance, Object[] args)
throws PropertyException, NoSuchMethodException
{
Class[] types = new Class[ args.length ];
for (int i = 0; i < args.length; i++) {
try {
types[i] = args[i].getClass();
} catch (NullPointerException e) {
types[i] = null;
}
}
for (int i = 0; i < _methods.size(); i++) {
Method m = (Method) _methods.elementAt(i);
Class[] sig = m.getParameterTypes();
if (matches(sig,types)) {
return PropertyOperator.invoke(m,instance,args);
}
}
// not found
StringBuffer msg = new StringBuffer();
msg.append("No method ");
msg.append(getName());
msg.append("(");
for (int i = 0; i < args.length; i++) {
if (i > 0) {
msg.append(",");
}
msg.append((args[i] == null) ? "null" : args[i].getClass().toString());
}
msg.append(") on object ");
msg.append(instance);
throw new PropertyException(msg.toString(),null);
}
}
abstract class MethodAccessor extends Accessor
{
Method _getMethod; // only one get method allowed
Method[] _setMethods = null; // may be multiple set methods
Class[] _setParams = null; // variable arg type for set meth N
int setCount = 0; // how many set methods do we have
abstract int numArgsGet();
abstract int numArgsSet();
MethodAccessor(final String name, final Method m, final Class[] params)
throws PropertyException
{
super(name);
addMethod(m,params);
}
final void addMethod(final Method m, Class[] params)
throws PropertyException
{
final int setArgsLength = numArgsSet();
final int getArgsLength = numArgsGet();
if (params.length == getArgsLength) {
_getMethod = m;
} else if (params.length == setArgsLength) {
setCount++;
if (_setMethods == null) {
_setMethods = new Method[1];
_setParams = new Class[1];
} else if (_setMethods.length <= setCount) {
Method[] tmpMethods = new Method[ (setCount + 1) * 2 ];
Class[] tmpParams = new Class[(setCount + 1) * 2 ];
System.arraycopy(_setMethods,0,tmpMethods,0,_setMethods.length);
System.arraycopy(_setParams,0,tmpParams,0,_setParams.length);
_setMethods = tmpMethods;
_setParams = tmpParams;
}
// record the method, and the type of the variable parameter
_setMethods[setCount - 1] = m;
_setParams[setCount - 1] = params[setArgsLength - 1];
} else {
throw new PropertyException("PropertyOperator FAILED for method "
+ m + "--please report this bug!",null);
}
}
final boolean setImpl(final Object inst, final Object[] args)
throws PropertyException, NoSuchMethodException
{
//which method to use? check params for first match
for (int i = 0; i < setCount; i++) {
Object arg = args[args.length - 1];
// XXX: null values are blocked by the next line
if (_setParams[i].isInstance(args[args.length - 1])) {
PropertyOperator.invoke(_setMethods[i],inst,args);
return true;
}
}
return false;
}
}
final class UnaryMethodAccessor extends MethodAccessor
{
UnaryMethodAccessor(final String name, final Method m, final Class[] params)
throws PropertyException
{
super(name,m,params);
}
final int numArgsGet() {
return 0;
}
final int numArgsSet() {
return 1;
}
final Object get(final Object instance)
throws PropertyException, NoSuchMethodException
{
return PropertyOperator.invoke(_getMethod,instance,null);
}
final boolean set(final Object instance, final Object value)
throws PropertyException, NoSuchMethodException
{
Object[] args = new Object[1];
args[0] = value;
return setImpl(instance,args);
}
}
final class BinaryMethodAccessor extends MethodAccessor
{
BinaryMethodAccessor(String name, Method m, Class[] params)
throws PropertyException
{
super( name,m,params);
}
final int numArgsGet() {
return 1;
}
final int numArgsSet() {
return 2;
}
final Object get(final Object instance, String prop)
throws PropertyException, NoSuchMethodException
{
Object[] args = new Object[1];
args[0] = prop;
return PropertyOperator.invoke(_getMethod,instance,args);
}
final boolean set(final Object instance, String prop, Object value)
throws PropertyException, NoSuchMethodException
{
Object[] args = new Object[2];
args[0] = prop;
args[1] = value;
return setImpl(instance,args);
}
}
1.1 jakarta-velocity/src/java/org/apache/velocity/introspection/PropertyReference.java
Index: PropertyReference.java
===================================================================
/*
* Copyright (c) 1998, 1999 Semiotek Inc. All Rights Reserved.
*
* This software is the confidential intellectual property of
* of Semiotek Inc.; it is copyrighted and licensed, not sold.
* You may use it under the terms of the GNU General Public License,
* version 2, as published by the Free Software Foundation. If you
* do not want to use the GPL, you may still use the software after
* purchasing a proprietary developers license from Semiotek Inc.
*
* 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 the attached License.html file for details, or contact us
* by e-mail at info@semiotek.com to get a copy.
*/
package org.apache.velocity.introspection;
//import org.webmacro.*;
import org.apache.velocity.Context;
/**
* This is a generic interface which PropertyOperator users may
* wish to subclass from. The intent is that implementors of this
* class may use PropertyOperator to examine the context in order
* to extract some kind of value, which is returned.
*/
public interface PropertyReference
{
/**
* Apply this object to the supplied context and return
* the appropriate reference.
* @param context An object which may be introspected for information
* @return The appropriate value given this context
* @exception ContextException required data not found in context
*/
public Object evaluate(Context context);
//throws ContextException;
}
1.1 jakarta-velocity/src/java/org/apache/velocity/introspection/RefMap.java
Index: RefMap.java
===================================================================
package org.apache.velocity.introspection;
import java.util.HashMap;
/**
* RefMap is a very specialized data structure that is probably
* not suitable for most uses. It is a faster version of a
* HashMap type structure which is broken in the following
* ways, for efficiency: (1) it does not use object.equals()
* to compare keys, so you must use the identical instance
* as the key with get() as with put(). (2) it never exands
* itself, but instead throws an exception when it fills up.
* you can use the copy() method to make a bigger one in
* this case. (3) you cannot remove data from the map.
* (4) it is not synchronized on reads, but it does guarantee
* that the value read did at one point belong to the key
* supplied (and not to some other key).
* <p>
* RefMap was implemented to optimize a particular section
* of PropertyOperator where the above characteristics are
* acceptable and the speed increase desirable. Do not use
* it in any other context unless you are really sure you
* understand just how inconvenient the above limitations
* can be.
*/
final public class RefMap {
final Object _key[];
final Object _value[];
int _size;
public RefMap() {
this(1001);
}
public RefMap(int size) {
_key = new Object[size];
_value = new Object[size];
_size = 0;
}
public void put(Object key, Object value)
throws java.lang.IndexOutOfBoundsException
{
if (key == null) {
return;
}
int loc = key.hashCode() % _key.length;
if (loc < 0) { loc *= -1; }
if ((_key[loc] != null) || (_key[loc] == key)) {
int i = loc + 1;
while (i != loc) {
if (i == _key.length) {
i = 0;
} else if ((_key[i] == null) || (_key[loc] == key)) {
break;
} else {
i++;
}
}
loc = i;
}
if ((_key[loc] == null) || (_key[loc] == key)) {
_size++;
_value[loc] = value;
synchronized(_key) { } // control update order
_key[loc] = key;
} else {
throw new java.lang.IndexOutOfBoundsException("RefMap is Full, use realloc");
}
}
/**
* WARNING: this method is unsynchronized and it might
* return invalid data. This could happen if one thread
* attempted to write at the same moment as another
* thread attempted a read.
*/
public Object get(Object key) {
if (key == null) {
return null;
}
int loc = key.hashCode() % _key.length;
if (loc < 0) { loc *= -1; }
if (_key[loc] == key) {
return _value[loc];
} else {
int i = loc + 1;
while (i != loc) {
if (i == _key.length) {
i = 0;
} else if (_key[i] == key) {
return _value[i];
} else {
i++;
}
}
return null;
}
}
public RefMap copy(int size)
throws IndexOutOfBoundsException
{
if (_size > size) {
throw new IndexOutOfBoundsException("New map must be bigger than the old map");
}
RefMap nm = new RefMap(size);
for (int i = 0; i < _key.length; i++) {
if (_key[i] != null) {
nm.put(_key[i],_value[i]);
}
}
return nm;
}
public void clear() {
for (int i = 0; i < _key.length; i++) {
_key[i] = null;
synchronized(_key) { } // control update order
_value[i] = null;
}
}
public int size() {
return _size;
}
public int capacity() {
return _key.length;
}
public static void main(String arg[]) {
RefMap rm = new RefMap(11);
HashMap hm = new HashMap();
Integer[] vals = new Integer[arg.length];
for(int i = 0; i < arg.length; i++) {
arg[i] = new String(arg[i]);
}
for(int i = 0; i < arg.length; i++) {
vals[i] = new Integer(i);
}
for(int i = 0; i < arg.length; i++) {
rm.put(arg[i],vals[i]);
hm.put(arg[i],vals[i]);
}
for(int i = 0; i < arg.length; i++) {
System.out.println(arg[i] + " :rm: " + rm.get(arg[i]));
}
System.out.println();
for(int i = 0; i < arg.length; i++) {
System.out.println(arg[i] + " :hm: " + hm.get(arg[i]));
}
long time;
int size = 10;
for(int q = 0; q < 200; q++) {
time = System.currentTimeMillis();
for(int i = 1; i < size; i++) {
for(int j = 1; j < arg.length; j++) {
Object o = rm.get(arg[j]);
}
}
System.out.println("refmap : " + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
for(int i = 1; i < size; i++) {
for(int j = 1; j < arg.length; j++) {
Object o = rm.get(arg[j]);
}
}
System.out.println("hashmap: " + (System.currentTimeMillis() - time));
}
}
}