You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jmeter-dev@jakarta.apache.org by se...@apache.org on 2007/10/03 20:55:39 UTC
svn commit: r581687 - in /jakarta/jmeter/trunk:
src/jorphan/org/apache/jorphan/reflect/Functor.java
test/src/org/apache/jorphan/reflect/
test/src/org/apache/jorphan/reflect/TestFunctor.java xdocs/changes.xml
Author: sebb
Date: Wed Oct 3 11:55:38 2007
New Revision: 581687
URL: http://svn.apache.org/viewvc?rev=581687&view=rev
Log:
Functor code tightened up; Functor can now be used with interfaces, as well as pre-defined targets and parameters.
Added:
jakarta/jmeter/trunk/test/src/org/apache/jorphan/reflect/
jakarta/jmeter/trunk/test/src/org/apache/jorphan/reflect/TestFunctor.java (with props)
Modified:
jakarta/jmeter/trunk/src/jorphan/org/apache/jorphan/reflect/Functor.java
jakarta/jmeter/trunk/xdocs/changes.xml
Modified: jakarta/jmeter/trunk/src/jorphan/org/apache/jorphan/reflect/Functor.java
URL: http://svn.apache.org/viewvc/jakarta/jmeter/trunk/src/jorphan/org/apache/jorphan/reflect/Functor.java?rev=581687&r1=581686&r2=581687&view=diff
==============================================================================
--- jakarta/jmeter/trunk/src/jorphan/org/apache/jorphan/reflect/Functor.java (original)
+++ jakarta/jmeter/trunk/src/jorphan/org/apache/jorphan/reflect/Functor.java Wed Oct 3 11:55:38 2007
@@ -22,147 +22,315 @@
import java.util.Arrays;
import org.apache.jorphan.logging.LoggingManager;
+import org.apache.jorphan.util.JMeterError;
import org.apache.log.Logger;
/**
- * @author mstover
+ * Implements function call-backs.
+ *
+ * Functors may be defined for instance objects or classes.
+ *
+ * The method is created on first use, which allows the invokee (class or instance)
+ * to be omitted from the constructor.
+ *
+ * The class name takes precedence over the instance.
+ *
+ * If a functor is created with a particular instance, then that is used for all future calls;
+ * if an object is provided, it is ignored.
+ * This allows easy override of the table model behaviour.
+ *
+ * If an argument list is provided in the constructor, then that is ignored in subsequent invoke() calls.
+ *
+ * Usage:
+ * f = new Functor("methodName")
+ * o = f.invoke(object) - OR -
+ * o = f.invoke(object,params)
+ *
+ * f2 = new Functor(object,"methodName");
+ * o = f2.invoke() - OR -
+ * o = f2.invoke(params)
+ *
+ * f3 = new Functor(class,"methodName");
+ * o = f3.invoke(object) - will be ignored
+ * o = f3.invoke() - OR -
+ * o = f3.invoke(params)
+ * o = f3.invoke(object,params) - object will be ignored
+ *
*/
public class Functor {
- private static Logger log = LoggingManager.getLoggerForClass();
+ private static final Logger log = LoggingManager.getLoggerForClass();
- Object invokee;
+ /*
+ * If non-null, then any object provided to invoke() is ignored.
+ */
+ private final Object invokee;
+
+ /*
+ * Class to be used to create the Method.
+ * Will be non-null if either Class or Object was provided during construction.
+ *
+ * Can be used instead of invokee, e.g. when using interfaces.
+ */
+ private final Class clazz;
- String methodName;
+ // Methondname must always be provided.
+ private final String methodName;
- Object[] args;
+ /*
+ * If non-null, then any argument list passed to invoke() will be ignored.
+ */
+ private Object[] args;
- Class[] types;
+ /*
+ * Argument types used to create the method.
+ * May be provided explicitly, or derived from the constructor argument list.
+ */
+ private final Class[] types;
- Method methodToInvoke;
+ /*
+ * This depends on the class or invokee and either args or types;
+ * it is set once by doCreateMethod(), which must be the only method to access it.
+ */
+ private Method methodToInvoke;
+
+ Functor(){
+ throw new IllegalArgumentException("Must provide at least one argument");
+ }
/**
* Create a functor with the invokee and a method name.
*
- * @param invokee
- * @param methodName
+ * The invokee will be used in all future invoke calls.
+ *
+ * @param _invokee object on which to invoke the method
+ * @param _methodName method name
+ */
+ public Functor(Object _invokee, String _methodName) {
+ this(null, _invokee, _methodName, null, null);
+ }
+
+ /**
+ * Create a functor from class and method name.
+ * This is useful for methods defined in interfaces.
+ *
+ * The actual invokee must be provided in all invoke() calls,
+ * and must be an instance of the class.
+ *
+ * @param _clazz class to be used
+ * @param _methodName method name
*/
- public Functor(Object invokee, String methodName) {
- this(methodName);
- this.invokee = invokee;
+ public Functor(Class _clazz, String _methodName) {
+ this(_clazz, null, _methodName, null, null);
}
/**
* Create a functor with the invokee, method name, and argument class types.
*
- * @param invokee
- * @param methodName
+ * The invokee will be ignored in any invoke() calls.
+ *
+ * @param _invokee object on which to invoke the method
+ * @param _methodName method name
* @param types
*/
- public Functor(Object invokee, String methodName, Class[] types) {
- this(invokee, methodName);
- this.types = types;
+ public Functor(Object _invokee, String _methodName, Class[] types) {
+ this(null, _invokee, _methodName, null, types);
}
/**
* Create a functor with just the method name.
*
- * @param methodName
+ * The invokee and any parameters must be provided in all invoke() calls.
+ *
+ * @param _methodName method name
*/
- public Functor(String methodName) {
- this.methodName = methodName;
+ public Functor(String _methodName) {
+ this(null, null, _methodName, null, null);
}
/**
* Create a functor with the method name and argument class types.
*
- * @param methodName
- * @param types
+ * The invokee must be provided in all invoke() calls
+ *
+ * @param _methodName method name
+ * @param _types parameter types
*/
- public Functor(String methodName, Class[] types) {
- this(methodName);
- this.types = types;
+ public Functor(String _methodName, Class[] _types) {
+ this(null, null, _methodName, null, _types);
}
/**
* Create a functor with an invokee, method name, and argument values.
*
- * @param invokee
- * @param methodName
- * @param args
+ * The invokee will be ignored in any invoke() calls.
+ *
+ * @param _invokee object on which to invoke the method
+ * @param _methodName method name
+ * @param _args arguments to be passed to the method
*/
- public Functor(Object invokee, String methodName, Object[] args) {
- this(invokee, methodName);
- this.args = args;
+ public Functor(Object _invokee, String _methodName, Object[] _args) {
+ this(null, _invokee, _methodName, _args, null);
}
- public Functor(String methodName, Object[] args) {
- this(methodName);
- this.args = args;
+ /**
+ * Create a functor from method name and arguments.
+ *
+ * The class will be determined from the first invoke call.
+ * All invoke calls must include a target object;
+ * which must be of the same type as the initial invokee.
+ *
+ * @param _methodName method name
+ * @param _args
+ */
+ public Functor(String _methodName, Object[] _args) {
+ this(null, null, _methodName, _args, null);
}
/**
- * Create a functor with an invokee, method name, argument values, and
- * argument class types.
+ * Create a functor from various different combinations of parameters.
*
- * @param invokee
- * @param methodName
- * @param args
- * @param types
+ * @param _clazz class containing the method
+ * @param _invokee invokee to use for the method call
+ * @param _methodName the method name (required)
+ * @param _args arguments to be used
+ * @param _types types of arguments to be used
+ *
+ * @throws IllegalArgumentException if:
+ * - methodName is null
+ * - both class and invokee are specified
+ * - both arguments and types are specified
*/
- public Functor(Object invokee, String methodName, Object[] args, Class[] types) {
- this(invokee, methodName, args);
- this.types = types;
+ private Functor(Class _clazz, Object _invokee, String _methodName, Object[] _args, Class[] _types) {
+ if (_methodName == null){
+ throw new IllegalArgumentException("Methodname must not be null");
+ }
+ if (_clazz != null && _invokee != null){
+ throw new IllegalArgumentException("Cannot provide both Class and Object");
+ }
+ if (_args != null && _types != null){
+ throw new IllegalArgumentException("Cannot provide both arguments and argument types");
+ }
+ // If class not provided, default to invokee class, else null
+ this.clazz = _clazz != null ? _clazz : (_invokee != null ? _invokee.getClass() : null);
+ this.invokee = _invokee;
+ this.methodName = _methodName;
+ this.args = _args;
+ // If types not provided, default to argument types, else null
+ this.types = _types != null ? _types : (_args != null ? _getTypes(_args) : null);
}
- public Object invoke() {
+ //////////////////////////////////////////
+
+ /*
+ * Low level invocation routine.
+ *
+ * Should only be called after any defaults have been applied.
+ *
+ */
+ private Object doInvoke(Class _class, Object _invokee, Object[] _args) {
+ Class[] argTypes = getTypes(_args);
try {
- return createMethod(getTypes()).invoke(invokee, getArgs());
+ Method method = doCreateMethod(_class , argTypes);
+ if (method == null){
+ throw new JMeterError("Can't find method "+_class+typesToString(argTypes));
+ }
+ return method.invoke(_invokee, _args);
} catch (Exception e) {
- final String message = "Trouble functing method: "+methodName+" invokee: "+invokee.getClass().getName();
+ final String message = "Trouble functing: "
+ +_class.getName()
+ +"."+methodName+"(...) : "
+ +" invokee: "+_invokee
+ +" "+e.getMessage();
log.warn(message, e);
- throw new org.apache.jorphan.util.JMeterError(message,e); // JDK1.4
+ throw new JMeterError(message,e);
+ }
+ }
+
+ /**
+ * Invoke a Functor, which must have been created with either a class name or object.
+ *
+ * @return the object if any
+ */
+ public Object invoke() {
+ if (invokee == null) {
+ throw new IllegalStateException("Cannot call invoke() - invokee not known");
}
+ // If invokee was provided, then clazz has been set up
+ return doInvoke(clazz, invokee, getArgs());
}
+ /**
+ * Invoke the method on a given object.
+ *
+ * @param p_invokee - provides the object to call; ignored if the class or object were provided to the constructor
+ * @return the value
+ */
public Object invoke(Object p_invokee) {
- this.invokee = p_invokee;
- return invoke();
+ return invoke(p_invokee, getArgs());
}
+ /**
+ * Invoke the method with the provided parameters.
+ *
+ * The invokee must have been provided in the constructor.
+ *
+ * @param p_args parameters for the method
+ * @return the value
+ */
public Object invoke(Object[] p_args) {
- this.args = p_args;
- return invoke();
+ if (invokee == null){
+ throw new IllegalStateException("Invokee was not provided in constructor");
+ }
+ // If invokee was provided, then clazz has been set up
+ return doInvoke(clazz, invokee, args != null? args : p_args);
}
+ /**
+ * Invoke the method on the invokee with the provided parameters.
+ *
+ * The invokee must agree with the class (if any) provided at construction time.
+ *
+ * If the invokee was provided at construction time, then this invokee will be ignored.
+ * If actual arguments were provided at construction time, then arguments will be ignored.
+ *
+ */
public Object invoke(Object p_invokee, Object[] p_args) {
- this.args = p_args;
- this.invokee = p_invokee;
- return invoke();
+ return doInvoke(clazz != null ? clazz : p_invokee.getClass(), // Use constructor class if present
+ invokee != null ? invokee : p_invokee, // use invokee if provided
+ args != null? args : p_args);// use argumenrs if provided
}
- private Method createMethod(Class[] p_types) {
- log.debug("Trying to functorize invokee: " + invokee.getClass().getName() + " method: " + methodName
+ /*
+ * Low-level (recursive) routine to define the method - if not already defined.
+ * Synchronized to protect access to methodToInvoke.
+ */
+ private synchronized Method doCreateMethod(Class p_class, Class[] p_types) {
+ if (log.isDebugEnabled()){
+ log.debug("doCreateMethod() using "+this.toString()
+ +"class="
+ + p_class.getName()
+ " types: " + Arrays.asList(p_types));
+ }
if (methodToInvoke == null) {
try {
- methodToInvoke = invokee.getClass().getMethod(methodName, p_types);
+ methodToInvoke = p_class.getMethod(methodName, p_types);
} catch (Exception e) {
for (int i = 0; i < p_types.length; i++) {
Class primitive = getPrimitive(p_types[i]);
if (primitive != null) {
- methodToInvoke = createMethod(getNewArray(i, primitive, p_types));
+ methodToInvoke = doCreateMethod(p_class, getNewArray(i, primitive, p_types));
if (methodToInvoke != null)
return methodToInvoke;
}
Class[] interfaces = p_types[i].getInterfaces();
for (int j = 0; j < interfaces.length; j++) {
- methodToInvoke = createMethod(getNewArray(i, interfaces[j], p_types));
+ methodToInvoke = doCreateMethod(p_class,getNewArray(i, interfaces[j], p_types));
if (methodToInvoke != null) {
return methodToInvoke;
}
}
Class parent = p_types[i].getSuperclass();
- methodToInvoke = createMethod(getNewArray(i, parent, p_types));
+ methodToInvoke = doCreateMethod(p_class,getNewArray(i, parent, p_types));
if (methodToInvoke != null) {
return methodToInvoke;
}
@@ -172,7 +340,7 @@
return methodToInvoke;
}
- /*
+ /**
* Check if a read Functor method is valid.
*
* @deprecated ** for use by Unit test code only **
@@ -181,16 +349,15 @@
*/
public boolean checkMethod(Object _invokee){
Method m = null;
- this.invokee=_invokee;
try {
- m = createMethod(getTypes());
+ m = doCreateMethod(_invokee.getClass(), getTypes(args));
} catch (Exception e){
// ignored
}
return null != m;
}
- /*
+ /**
* Check if a write Functor method is valid.
*
* @deprecated ** for use by Unit test code only **
@@ -199,9 +366,8 @@
*/
public boolean checkMethod(Object _invokee, Class c){
Method m = null;
- this.invokee=_invokee;
try {
- m = createMethod(new Class[]{c});
+ m = doCreateMethod(_invokee.getClass(), new Class[]{c});
} catch (Exception e){
// ignored
}
@@ -210,16 +376,38 @@
public String toString(){
StringBuffer sb = new StringBuffer(100);
- sb.append("method: ");
- sb.append(methodName);
+ if (clazz != null){
+ sb.append(clazz.getName());
+ }
if (invokee != null){
- sb.append(" invokee: ");
- sb.append(invokee.getClass().getName());
+ sb.append("@");
+ sb.append(System.identityHashCode(invokee));
+ }
+ sb.append(".");
+ sb.append(methodName);
+ typesToString(sb,types);
+ return sb.toString();
+ }
+
+ private void typesToString(StringBuffer sb,Class[] _types) {
+ sb.append("(");
+ if (_types != null){
+ for(int i=0; i < _types.length; i++){
+ if (i>0) sb.append(",");
+ sb.append(_types[i].getName());
+ }
}
+ sb.append(")");
+ }
+
+ private String typesToString(Class[] argTypes) {
+ StringBuffer sb = new StringBuffer();
+ typesToString(sb,argTypes);
return sb.toString();
}
- protected Class getPrimitive(Class t) {
+ private Class getPrimitive(Class t) {
+ if (t==null) return null;
if (t.equals(Integer.class)) {
return int.class;
} else if (t.equals(Long.class)) {
@@ -240,7 +428,7 @@
return null;
}
- protected Class[] getNewArray(int i, Class replacement, Class[] orig) {
+ private Class[] getNewArray(int i, Class replacement, Class[] orig) {
Class[] newArray = new Class[orig.length];
for (int j = 0; j < newArray.length; j++) {
if (j == i) {
@@ -252,21 +440,26 @@
return newArray;
}
- private Class[] getTypes() {
- if (types == null) // do only once per functor instance. Could
- // cause errors if functor used for multiple
- // same-named-different-parametered methods.
+ // TODO - should this be synchronised?
+ private Class[] getTypes(Object[] _args) {
+ if (types == null)
{
- if (args != null) {
- types = new Class[args.length];
- for (int i = 0; i < args.length; i++) {
- types[i] = args[i].getClass();
- }
- } else {
- types = new Class[0];
- }
+ return _getTypes(_args);
}
return types;
+ }
+
+ private static Class[] _getTypes(Object[] _args) {
+ Class[] _types;
+ if (_args != null) {
+ _types = new Class[_args.length];
+ for (int i = 0; i < _args.length; i++) {
+ _types[i] = _args[i].getClass();
+ }
+ } else {
+ _types = new Class[0];
+ }
+ return _types;
}
private Object[] getArgs() {
Added: jakarta/jmeter/trunk/test/src/org/apache/jorphan/reflect/TestFunctor.java
URL: http://svn.apache.org/viewvc/jakarta/jmeter/trunk/test/src/org/apache/jorphan/reflect/TestFunctor.java?rev=581687&view=auto
==============================================================================
--- jakarta/jmeter/trunk/test/src/org/apache/jorphan/reflect/TestFunctor.java (added)
+++ jakarta/jmeter/trunk/test/src/org/apache/jorphan/reflect/TestFunctor.java Wed Oct 3 11:55:38 2007
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jorphan.reflect;
+
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.jmeter.junit.JMeterTestCase;
+import org.apache.jorphan.util.JMeterError;
+
+/*
+ * Unit tests for classes that use Functors
+ *
+ */
+public class TestFunctor extends JMeterTestCase {
+
+ interface HasName {
+ String getName();
+ }
+
+ interface HasString {
+ String getString(String s);
+ }
+
+ class Test1 implements HasName {
+ private final String name;
+ public Test1(){
+ this("");
+ }
+ public Test1(String s){
+ name=s;
+ }
+ public String getName(){
+ return name;
+ }
+ public String getString(String s){
+ return s;
+ }
+ }
+ class Test1a extends Test1{
+ Test1a(){
+ super("1a");
+ }
+ Test1a(String s){
+ super("1a:"+s);
+ }
+ public String getName(){
+ return super.getName()+".";
+ }
+ }
+ class Test2 implements HasName, HasString {
+ private final String name;
+ public Test2(){
+ this("");
+ }
+ public Test2(String s){
+ name=s;
+ }
+ public String getName(){
+ return name;
+ }
+ public String getString(String s){
+ return s;
+ }
+ }
+
+ public TestFunctor(String arg0) {
+ super(arg0);
+ }
+
+ public void testName() throws Exception{
+ Functor f1 = new Functor("getName");
+ Functor f2 = new Functor("getName");
+ Functor f1a = new Functor("getName");
+ Test1 t1 = new Test1("t1");
+ Test2 t2 = new Test2("t2");
+ Test1a t1a = new Test1a("aa");
+ assertEquals("t1",f1.invoke(t1));
+ //assertEquals("t1",f1.invoke());
+ try {
+ f1.invoke(t2);
+ fail("Should have generated error");
+ } catch (JMeterError e){
+
+ }
+ assertEquals("t2",f2.invoke(t2));
+ //assertEquals("t2",f2.invoke());
+ assertEquals("1a:aa.",f1a.invoke(t1a));
+ //assertEquals("1a:aa.",f1a.invoke());
+ try {
+ f1a.invoke(t1);// can't call invoke using super class
+ fail("Should have generated error");
+ } catch (JMeterError e){
+
+ }
+ // OK (currently) to invoke using sub-class
+ assertEquals("1a:aa.",f1.invoke(t1a));
+ //assertEquals("1a:aa.",f1.invoke());// N.B. returns different result from before
+ }
+
+ public void testNameTypes() throws Exception{
+ Functor f = new Functor("getString",new Class[]{String.class});
+ Functor f2 = new Functor("getString");// Args will be provided later
+ Test1 t1 = new Test1("t1");
+ assertEquals("x1",f.invoke(t1,new String[]{"x1"}));
+ try {
+ assertEquals("x1",f.invoke(t1));
+ fail("Should have generated an Exception");
+ } catch (JMeterError ok){
+ }
+ assertEquals("x2",f2.invoke(t1,new String[]{"x2"}));
+ try {
+ assertEquals("x2",f2.invoke(t1));
+ fail("Should have generated an Exception");
+ } catch (JMeterError ok){
+ }
+ }
+ public void testObjectName() throws Exception{
+ Test1 t1 = new Test1("t1");
+ Test2 t2 = new Test2("t2");
+ Functor f1 = new Functor(t1,"getName");
+ assertEquals("t1",f1.invoke(t1));
+ assertEquals("t1",f1.invoke(t2)); // should use original object
+ }
+
+ // Check how Class definition behaves
+ public void testClass() throws Exception{
+ Test1 t1 = new Test1("t1");
+ Test1 t1a = new Test1a("t1a");
+ Test2 t2 = new Test2("t2");
+ Functor f1 = new Functor(HasName.class,"getName");
+ assertEquals("t1",f1.invoke(t1));
+ assertEquals("1a:t1a.",f1.invoke(t1a));
+ assertEquals("t2",f1.invoke(t2));
+ try {
+ f1.invoke();
+ fail("Should have failed");
+ } catch (IllegalStateException ok){
+
+ }
+ Functor f2 = new Functor(HasString.class,"getString");
+ assertEquals("xyz",f2.invoke(t2,new String[]{"xyz"}));
+ try {
+ f2.invoke(t1,new String[]{"xyz"});
+ fail("Should have failed");
+ } catch (JMeterError ok){
+
+ }
+ Functor f3 = new Functor(t2,"getString");
+ assertEquals("xyz",f3.invoke(t2,new Object[]{"xyz"}));
+
+ Properties p = new Properties();
+ p.put("Name","Value");
+ Functor fk = new Functor(Map.Entry.class,"getKey");
+ Functor fv = new Functor(Map.Entry.class,"getValue");
+ Object o = p.entrySet().iterator().next();
+ assertEquals("Name",fk.invoke(o));
+ assertEquals("Value",fv.invoke(o));
+ }
+
+ public void testBadParameters() throws Exception{
+ try {
+ new Functor(null);
+ fail("should have generated IllegalArgumentException;");
+ } catch (IllegalArgumentException ok){}
+ try {
+ new Functor(null,new Class[]{});
+ fail("should have generated IllegalArgumentException;");
+ } catch (IllegalArgumentException ok){}
+ try {
+ new Functor(null,new Object[]{});
+ fail("should have generated IllegalArgumentException;");
+ } catch (IllegalArgumentException ok){}
+ try {
+ new Functor(String.class,null);
+ fail("should have generated IllegalArgumentException;");
+ } catch (IllegalArgumentException ok){}
+ try {
+ new Functor(new Object(),null);
+ fail("should have generated IllegalArgumentException;");
+ } catch (IllegalArgumentException ok){}
+ try {
+ new Functor(new Object(),null, new Class[]{});
+ fail("should have generated IllegalArgumentException;");
+ } catch (IllegalArgumentException ok){}
+ try {
+ new Functor(new Object(),null, new Object[]{});
+ fail("should have generated IllegalArgumentException;");
+ } catch (IllegalArgumentException ok){}
+ }
+ public void testIllegalState() throws Exception{
+ Functor f = new Functor("method");
+ try {
+ f.invoke();
+ fail("should have generated IllegalStateException;");
+ } catch (IllegalStateException ok){}
+ try {
+ f.invoke(new Object[]{});
+ fail("should have generated IllegalStateException;");
+ } catch (IllegalStateException ok){}
+ }
+}
Propchange: jakarta/jmeter/trunk/test/src/org/apache/jorphan/reflect/TestFunctor.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jakarta/jmeter/trunk/test/src/org/apache/jorphan/reflect/TestFunctor.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision
Modified: jakarta/jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jakarta/jmeter/trunk/xdocs/changes.xml?rev=581687&r1=581686&r2=581687&view=diff
==============================================================================
--- jakarta/jmeter/trunk/xdocs/changes.xml (original)
+++ jakarta/jmeter/trunk/xdocs/changes.xml Wed Oct 3 11:55:38 2007
@@ -47,6 +47,11 @@
<li>Test Plan items can now only be dropped/pasted into parts of the tree where they are allowed</li>
</ul>
+<h4>Non-functional Improvements</h4>
+<ul>
+<li>Functor code tightened up; Functor can now be used with interfaces, as well as pre-defined targets and parameters.</li>
+</ul>
+
<!-- =================== -->
<h3>Version 2.3</h3>
---------------------------------------------------------------------
To unsubscribe, e-mail: jmeter-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: jmeter-dev-help@jakarta.apache.org