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 js...@apache.org on 2004/01/28 11:35:26 UTC

cvs commit: jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui GenericTestBeanCustomizer.java SharedCustomizer.java PackageTest.java TestBeanGUI.java TestElementEditor.java BeanInfoSupport.java WrapperEditor.java

jsalvata    2004/01/28 02:35:26

  Modified:    src/core/org/apache/jmeter/testbeans/gui PackageTest.java
                        TestBeanGUI.java TestElementEditor.java
                        BeanInfoSupport.java WrapperEditor.java
  Added:       src/core/org/apache/jmeter/testbeans/gui
                        GenericTestBeanCustomizer.java
                        SharedCustomizer.java
  Log:
  * Add bean customizer support.
  * Factor out the bean GUI to a GenericTestBeanCustomizer.
  
  Revision  Changes    Path
  1.3       +30 -24    jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/PackageTest.java
  
  Index: PackageTest.java
  ===================================================================
  RCS file: /home/cvs/jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/PackageTest.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- PackageTest.java	21 Jan 2004 13:50:31 -0000	1.2
  +++ PackageTest.java	28 Jan 2004 10:35:12 -0000	1.3
  @@ -116,7 +116,7 @@
   			beanInfo= Introspector.getBeanInfo(testBeanClass, TestBean.class);
   			bundle= (ResourceBundle) beanInfo
   				.getBeanDescriptor()
  -				.getValue(TestBeanGUI.RESOURCE_BUNDLE);
  +				.getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE);
   		}
   		catch (IntrospectionException e)
   		{
  @@ -127,6 +127,11 @@
   		if (bundle == null) throw new Error("This can't happen!");
   	}
   	
  +    public void tearDown()
  +    {
  +        JMeterUtils.setLocale(Locale.getDefault());
  +    }
  +    
   	public void runTest()
   	{
   		if (bundle == defaultBundle) checkAllNecessaryKeysPresent();
  @@ -175,10 +180,11 @@
   			bundle.getString(name+".displayName");
   			//bundle.getString(name+".shortDescription"); NOT MANDATORY
   
  -			String group= (String)descriptors[i].getValue(TestBeanGUI.GROUP);
  -			if (group != null) bundle.getString(group+".displayName");
  -		}
  -	}
  +            String group= (String)descriptors[i]
  +                    .getValue(GenericTestBeanCustomizer.GROUP);
  +            if (group != null) bundle.getString(group+".displayName");
  +        }
  +    }
   
   	public static Test suite() throws Exception
   	{
  @@ -196,24 +202,24 @@
   					new Class[] { TestBean.class })
   				.iterator();
   
  -		while (iter.hasNext())
  -		{
  -			Class testBeanClass= Class.forName((String)iter.next());
  -			JMeterUtils.setLocale(new Locale(defaultLanguage,""));
  -			ResourceBundle defaultBundle;
  -			try
  -			{
  -				defaultBundle= (ResourceBundle)
  -					Introspector.getBeanInfo(testBeanClass, TestBean.class)
  -					.getBeanDescriptor()
  -					.getValue(TestBeanGUI.RESOURCE_BUNDLE);
  -			}
  -			catch (IntrospectionException e)
  -			{
  -				log.error("Can't get beanInfo for "+testBeanClass.getName(),
  -					e);
  -				throw new Error(e.toString()); // Programming error. Don't continue.
  -			}
  +        while (iter.hasNext())
  +        {
  +            Class testBeanClass= Class.forName((String)iter.next());
  +            JMeterUtils.setLocale(new Locale(defaultLanguage,""));
  +            ResourceBundle defaultBundle;
  +            try
  +            {
  +                defaultBundle= (ResourceBundle)
  +                    Introspector.getBeanInfo(testBeanClass, TestBean.class)
  +                    .getBeanDescriptor()
  +                    .getValue(GenericTestBeanCustomizer.RESOURCE_BUNDLE);
  +            }
  +            catch (IntrospectionException e)
  +            {
  +                log.error("Can't get beanInfo for "+testBeanClass.getName(),
  +                    e);
  +                throw new Error(e.toString()); // Programming error. Don't continue.
  +            }
   
   			if (defaultBundle == null)
   			{
  
  
  
  1.8       +205 -657  jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/TestBeanGUI.java
  
  Index: TestBeanGUI.java
  ===================================================================
  RCS file: /home/cvs/jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/TestBeanGUI.java,v
  retrieving revision 1.7
  retrieving revision 1.8
  diff -u -r1.7 -r1.8
  --- TestBeanGUI.java	16 Jan 2004 02:52:48 -0000	1.7
  +++ TestBeanGUI.java	28 Jan 2004 10:35:12 -0000	1.8
  @@ -2,7 +2,7 @@
    * ====================================================================
    * The Apache Software License, Version 1.1
    *
  - * Copyright (c) 2003 The Apache Software Foundation.  All rights
  + * Copyright (c) 2004 The Apache Software Foundation.  All rights
    * reserved.
    *
    * Redistribution and use in source and binary forms, with or without
  @@ -59,36 +59,26 @@
   
   import java.awt.BorderLayout;
   import java.awt.Component;
  -import java.awt.GridBagConstraints;
  -import java.awt.GridBagLayout;
  -import java.awt.Insets;
  -
   import java.beans.BeanInfo;
  +import java.beans.Customizer;
   import java.beans.IntrospectionException;
   import java.beans.Introspector;
   import java.beans.PropertyDescriptor;
  -import java.beans.PropertyEditor;
   import java.beans.PropertyEditorManager;
  -
  -import java.text.MessageFormat;
   import java.util.Arrays;
   import java.util.Collection;
  -import java.util.Comparator;
  +import java.util.HashMap;
   import java.util.LinkedList;
   import java.util.List;
  -import java.util.MissingResourceException;
  -import java.util.ResourceBundle;
  +import java.util.Map;
   
  -import javax.swing.BorderFactory;
  -import javax.swing.Box;
  -import javax.swing.JLabel;
  -import javax.swing.JPanel;
   import javax.swing.JPopupMenu;
   
   import org.apache.jmeter.assertions.Assertion;
   import org.apache.jmeter.config.ConfigElement;
   import org.apache.jmeter.control.Controller;
   import org.apache.jmeter.gui.AbstractJMeterGuiComponent;
  +import org.apache.jmeter.gui.JMeterGUIComponent;
   import org.apache.jmeter.gui.util.MenuFactory;
   import org.apache.jmeter.processor.PostProcessor;
   import org.apache.jmeter.processor.PreProcessor;
  @@ -102,6 +92,7 @@
   import org.apache.jmeter.testelement.property.JMeterProperty;
   import org.apache.jmeter.testelement.property.LongProperty;
   import org.apache.jmeter.testelement.property.NullProperty;
  +import org.apache.jmeter.testelement.property.PropertyIterator;
   import org.apache.jmeter.testelement.property.StringProperty;
   import org.apache.jmeter.testelement.property.TestElementProperty;
   import org.apache.jmeter.timers.Timer;
  @@ -111,126 +102,82 @@
   import org.apache.log.Logger;
   
   /**
  - * The GenericGUI is designed to provide developers with a mechanism to
  - * quickly implement GUIs for new components.
  + * JMeter GUI element editing for TestBean elements.
    * <p>
  - * It allows editing each of the public exposed properties of the
  - * edited type 'a la JavaBeans': as far as the types of those properties
  - * have an associated editor, there's no GUI development required. 
  + * The actual GUI is always a bean customizer: if the bean descriptor provides
  + * one, it will be used; otherwise, a GenericTestBeanCustomizer will be
  + * created for this purpose.
    * <p>
  - * TestBeanGUI understands the following PropertyDescriptor attributes:
  - * <dl>
  - * <dt>group: String</dt>
  - * <dd>Group under which the property should be shown in the GUI. The string is
  - * also used as a group title (but see comment on resourceBundle below). The
  - * default group is "".</dd>
  - * <dt>order: Integer</dt>
  - * <dd>Order in which the property will be shown in its group. A smaller
  - * integer means higher up in the GUI. The default order is 0. Properties
  - * of equal order are sorted alphabetically.</dd>
  - * <dt>tags: String[]</dt>
  - * <dd>List of values to be offered for the property in addition to those
  - * offered by its property editor.</dd>
  - * <dt>notUndefined: Boolean</dt>
  - * <dd>If true, the property should not be left undefined. A <b>default</b>
  - * attribute must be provided if this is set.</dd>
  - * <dd>notExpression: Boolean</dd>
  - * <dd>If true, the property content should always be constant: JMeter
  - * 'expressions' (strings using ${var}, etc...) can't be used.</dt>
  - * <dd>notOther: Boolean</dd>
  - * <dd>If true, the property content must always be one of the tags values or
  - * null.</dt>
  - * <dt>default: Object</dt>
  - * <dd>Initial value for the property's GUI. Must be provided and be non-null
  - * if <b>notUndefined</b> is set. Must be one of the provided tags (or null) if
  - * <b>notOther</b> is set.
  - * </dl>
  + * Those customizers deviate from the standards only in that, instead of a
  + * bean, they will receive a Map in the setObject call. This will be a property
  + * name to value Map. The customizer is also in charge of initializing empty
  + * Maps with sensible initial values.
    * <p>
  - * The following BeanDescriptor attributes are also understood:
  - * <dl>
  - * <dt>group.<i>group</i>.order: Integer</dt>
  - * <dd>where <b><i>group</i></b> is a group name used in a <b>group</b>
  - * attribute in one or more PropertyDescriptors. Defines the order in which
  - * the group will be shown in the GUI. A smaller integer means higher up
  - * in the GUI. The default order is 0. Groups of equal order are sorted
  - * alphabetically.</dd>
  - * <dt>resourceBundle: ResourceBundle</dt>
  - * <dd>A resource bundle to be used for GUI localization: group display names
  - * will be obtained from property "<b><i>group</i>.displayName</b>" if
  - * available (where <b><i>group</i></b> is the group name).
  - * </dl>
  + * If the provided Customizer class implements the SharedCustomizer
  + * interface, the same instance of the customizer will be reused for all
  + * beans of the type: setObject(map) can then be called multiple times.
  + * Otherwise, one separate instance will be used for each element.
  + * For efficiency reasons, most customizers should implement
  + * SharedCustomizer.
    */
  -public class TestBeanGUI extends AbstractJMeterGuiComponent
  +public class TestBeanGUI
  +    extends AbstractJMeterGuiComponent
  +    implements JMeterGUIComponent
   {
       private static Logger log = LoggingManager.getLoggerForClass();
   
  -	public static final String GROUP= "group";
  -	public static final String ORDER= "order";
  -	public static final String TAGS= "tags";
  -	public static final String NOT_UNDEFINED= "notUndefined";
  -	public static final String NOT_EXPRESSION= "notExpression";
  -	public static final String NOT_OTHER= "notOther";
  -	public static final String DEFAULT= "default";
  -	public static final String RESOURCE_BUNDLE= "resourceBundle";
  -	public static final String ORDER(String group) {
  -		return "group."+group+".order";
  -	}
  +    private Class testBeanClass;
  +    
  +    private BeanInfo beanInfo;
  +
  +    private PropertyDescriptor[] properties;
   
  -	public static final String DEFAULT_GROUP= "";
  +    private Class customizerClass;
   
       /**
  -     * Class of the objects being edited.
  +     * The single customizer if the customizer class implements
  +     * SharedCustomizer, null otherwise.
        */
  -    private Class testBeanClass;
  -    
  +    private Customizer customizer= null;
  +
       /**
  -     * BeanInfo object for the class of the objects being edited.
  +     * TestElement to Customizer map if customizer is null.
        */
  -    private BeanInfo beanInfo;
  +    private Map customizers= new HashMap();
   
       /**
  -     * Property descriptors from the beanInfo.
  +     * Index of the customizer in the JPanel's child component list:
        */
  -    private PropertyDescriptor[] descriptors;
  -
  +    private int customizerIndexInPanel;
  +    
       /**
  -     * Property editors -- or null if the property can't be edited.
  +     * The property name to value map that the active customizer
  +     * edits:
        */
  -    private PropertyEditor[] editors;
  -
  -	/**
  -	 * Message format for property field labels:
  -	 */
  -	private MessageFormat propertyFieldLabelMessage;
  -	
  -	/**
  -	 * Message format for property tooltips:
  -	 */
  -	private MessageFormat propertyToolTipMessage;
  -	
  -	static
  -	{
  -		List paths= new LinkedList();
  -		paths.add("org.apache.jmeter.testbeans.gui");
  -		paths.addAll(Arrays.asList(PropertyEditorManager.getEditorSearchPath()));
  -		String s= JMeterUtils.getPropDefault("propertyEditorSearchPath", null) ;
  -		if (s != null)
  -		{
  -			paths.addAll(Arrays.asList(JMeterUtils.split(s, ",", "")));
  -		}
  -		PropertyEditorManager.setEditorSearchPath((String[])paths.toArray(new String[0]));
  -	}
  +    private Map propertyMap= new HashMap();
   
       /**
  -     * Create a GUI for a given test bean type.
  -     * 
  -     * @param testBeanClass a subclass of TestBean
  -     * @see org.apache.jmeter.testbeans.TestBean 
  +     * Whether the GUI components have been created. 
        */
  +    private boolean initialized= false;
  +
  +    static
  +    {
  +        List paths= new LinkedList();
  +        paths.add("org.apache.jmeter.testbeans.gui");
  +        paths.addAll(Arrays.asList(PropertyEditorManager.getEditorSearchPath()));
  +        String s= JMeterUtils.getPropDefault("propertyEditorSearchPath", null) ;
  +        if (s != null)
  +        {
  +            paths.addAll(Arrays.asList(JMeterUtils.split(s, ",", "")));
  +        }
  +        PropertyEditorManager.setEditorSearchPath((String[])paths.toArray(new String[0]));
  +    }
  +
       public TestBeanGUI(Class testBeanClass)
       {
           super();
  -
  +        
           // A quick verification, just in case:
           if (! TestBean.class.isAssignableFrom(testBeanClass))
           {
  @@ -245,7 +192,6 @@
           try
           {
               beanInfo= Introspector.getBeanInfo(testBeanClass, TestBean.class);
  -            descriptors= beanInfo.getPropertyDescriptors();
           }
           catch (IntrospectionException e)
           {
  @@ -254,284 +200,54 @@
               throw new Error(e.toString()); // Programming error. Don't continue.
           }
   
  -		// Sort the property descriptors:
  -		Arrays.sort(descriptors, new PropertyComparator());
  -
  -        // Obtain the propertyEditors:
  -        editors= new PropertyEditor[descriptors.length];
  -        for (int i=0; i<descriptors.length; i++)
  -        {
  -            String name= descriptors[i].getName();
  -
  -            // Don't get editors for hidden or non-read-write properties:
  -            if (descriptors[i].isHidden()
  -                || descriptors[i].getReadMethod() == null
  -                || descriptors[i].getWriteMethod() == null)
  -            {
  -                log.debug("No editor for property "+name);
  -                editors[i]= null;
  -                continue;
  -            }
  -
  -            PropertyEditor propertyEditor;
  -            Class editorClass= descriptors[i].getPropertyEditorClass();
  -            
  -            if (log.isDebugEnabled())
  -            {
  -                log.debug("Property "+name
  -                        +" has editor class "+editorClass);
  -            }
  -            
  -            if (editorClass != null)
  -            {
  -                try
  -                {
  -                    propertyEditor= (PropertyEditor)editorClass.newInstance();
  -                }
  -                catch (InstantiationException e)
  -                {
  -                    log.error("Can't create property editor.", e);
  -                    throw new Error(e.toString());
  -                }
  -                catch (IllegalAccessException e)
  -                {
  -                    log.error("Can't create property editor.", e);
  -                    throw new Error(e.toString());
  -                }
  -            }
  -            else
  -            {
  -                Class c= descriptors[i].getPropertyType();
  -                propertyEditor= PropertyEditorManager.findEditor(c);
  -            }
  -
  -            if (log.isDebugEnabled())
  -            {
  -                log.debug("Property "+name
  -                        +" has property editor "+propertyEditor);
  -            }
  -            
  -            if (propertyEditor == null)
  -            {
  -                log.debug("No editor for property "+name);
  -                editors[i]= null;
  -                continue;
  -            }
  -            
  -            if (! propertyEditor.supportsCustomEditor())
  -            {
  -				propertyEditor= createWrapperEditor(
  -					propertyEditor, descriptors[i]);
  +        properties= beanInfo.getPropertyDescriptors();
  +        customizerClass= beanInfo.getBeanDescriptor().getCustomizerClass();
   
  -				if (log.isDebugEnabled())
  -				{
  -					log.debug("Editor for property "+name
  -							+" is wrapped in "+propertyEditor);
  -				}
  -            }
  -            
  -			editors[i]= propertyEditor;
  +        // Creation of the customizer and GUI initialization is delayed until the first
  +        // configure call. We don't need all that just to find out the static label, menu
  +        // categories, etc!
  +        initialized= false;
  +    }
   
  -			// Initialize the editor with the provided default value or null:
  -            setEditorValue(i, descriptors[i].getValue(DEFAULT));
  +    private Customizer createCustomizer()
  +    {
  +        try
  +        {
  +            return (Customizer)customizerClass.newInstance();
  +        }
  +        catch (InstantiationException e)
  +        {
  +            log.error("Could not instantiate customizer of class "+customizerClass, e);
  +            throw new Error(e.toString());
  +        }
  +        catch (IllegalAccessException e)
  +        {
  +            log.error("Could not instantiate customizer of class "+customizerClass, e);
  +            throw new Error(e.toString());
           }
  -
  -		// Obtain message formats:
  -		propertyFieldLabelMessage= new MessageFormat(	
  -			JMeterUtils.getResString("property_as_field_label"));
  -		propertyToolTipMessage= new MessageFormat(	
  -			JMeterUtils.getResString("property_tool_tip"));
  -
  -        // Initialize the GUI:
  -        init();
       }
   
  -	/**
  -	 * Find the default typeEditor and a suitable guiEditor for the given
  -	 * property descriptor, and combine them in a WrapperEditor.
  -	 * 
  -	 * @param typeEditor
  -	 * @param descriptor
  -	 * @return
  -	 */
  -	private WrapperEditor createWrapperEditor(
  -			PropertyEditor typeEditor, PropertyDescriptor descriptor)
  -	{
  -		String[] editorTags= typeEditor.getTags();
  -		String[] additionalTags= (String[])descriptor.getValue(TAGS);
  -		String[] tags= null;
  -		if (editorTags == null) tags= additionalTags;
  -		else if (additionalTags == null) tags= editorTags;
  -		else {
  -			tags= new String[editorTags.length+additionalTags.length];
  -			int j= 0;
  -			for (int i=0; i<editorTags.length; i++) tags[j++]= editorTags[i];
  -			for (int i=0; i<additionalTags.length; i++) tags[j++]= additionalTags[i];
  -		}
  -
  -		boolean notNull=
  -			Boolean.TRUE.equals(descriptor.getValue(NOT_UNDEFINED));
  -		boolean notExpression=
  -			Boolean.TRUE.equals(descriptor.getValue(NOT_EXPRESSION));
  -		boolean notOther=
  -			Boolean.TRUE.equals(descriptor.getValue(NOT_OTHER));
  -
  -		PropertyEditor guiEditor;
  -		if (notNull && tags==null)
  -		{
  -			guiEditor= new FieldStringEditor();
  -		}
  -		else
  -		{
  -			ComboStringEditor e= new ComboStringEditor();
  -			e.setNoUndefined(notNull);
  -			e.setNoEdit(notExpression && notOther);
  -			e.setTags(tags);
  -			
  -			guiEditor= e;
  -		}
  -
  -		WrapperEditor wrapper= new WrapperEditor(
  -			typeEditor, guiEditor,
  -			!notNull, // acceptsNull
  -			!notExpression, // acceptsExpressions
  -			!notOther // acceptsOther
  -			);
  -
  -		Object defaultValue= descriptor.getValue(DEFAULT);
  -		
  -		try
  -		{
  -			wrapper.setValue(defaultValue);
  -		}
  -		catch (IllegalArgumentException e)
  -		{
  -			log.error("The default value for property " + descriptor.getName()
  -				+" is not valid. Or a default value was not provided and "
  -				+" property attribute notUndefined is set to true.");
  -			throw new Error(e.toString()); // programming error, so bail out.
  -		}
  -
  -		if (guiEditor instanceof ComboStringEditor)
  -		{
  -			// Provide an initial edit value if necessary:
  -		
  -			/*
  -				What follows encodes this correspondence:
  -
  -				 ot  ex  nl -- default or 1st tag or ${}
  -				 ot  ex !nl -- "": use "" or last valid value
  -				 ot !ex  nl -- default or 1st tag or "" or last valid value :-(
  -				 ot !ex !nl -- "": use "" or last valid value
  -				!ot  ex  nl -- ${}
  -				!ot  ex !nl -- ${}
  -				!ot !ex  nl -- not necessary (not editable)
  -				!ot !ex !nl -- not necessary (not editable)
  -
  -				[ot=other, ex=expressions, nl=null]
  -
  -			*/ 
  -
  -			String v;
  -			if (notOther) v="${}";
  -			else if (notNull) v= "";
  -			else if (defaultValue != null) v= wrapper.getAsText();
  -			else if (tags != null && tags.length>0) v= tags[0];
  -			else if (notExpression) v= "";
  -			else v="${}";
  -
  -			((ComboStringEditor)guiEditor).setInitialEditValue(v);
  -		}
  -
  -		return wrapper;
  -	}
  -
  -	/**
  -	 * Set the value of the i-th property, properly reporting a possible failure.
  -	 * 
  -	 * @param i the index of the property in the descriptors and editors arrays
  -	 * @param value the value to be stored in the editor
  -	 * 
  -	 * @throws IllegalArgumentException if the editor refuses the value
  -	 */
  -	private void setEditorValue(int i, Object value)
  -		throws IllegalArgumentException
  -	{
  -		try
  -		{
  -			editors[i].setValue(value);
  -		}
  -		catch (IllegalArgumentException e)
  -		{
  -			log.error("Could not set value "
  -				+ ( value == null ? "NULL" : value.getClass().getName() )
  -				+ ":" + value
  -				+" for property "+descriptors[i].getName());
  -			throw e;
  -		}
  -	}
  -
  -    public String getStaticLabel() {
  +    /* (non-Javadoc)
  +     * @see org.apache.jmeter.gui.JMeterGUIComponent#getStaticLabel()
  +     */
  +    public String getStaticLabel()
  +    {
           if (beanInfo == null) return "null";
           return beanInfo.getBeanDescriptor().getDisplayName();
       }
   
       /* (non-Javadoc)
  -     * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(org.apache.jmeter.testelement.TestElement)
  +     * @see org.apache.jmeter.gui.JMeterGUIComponent#createTestElement()
        */
  -    public void configure(TestElement element)
  -    {
  -        super.configure(element);
  -
  -		for (int i=0; i<editors.length; i++)
  -		{
  -			if (editors[i] == null) continue;
  -			JMeterProperty jprop= element.getProperty(descriptors[i].getName());
  -			try
  -			{
  -				setEditorValue(i, jprop.getObjectValue());
  -			}
  -			catch (IllegalArgumentException e)
  -			{
  -				// I guess this can happen as a result of a bad
  -				// file read? In this case, it would be better to replace the
  -				// incorrect value with anything valid, e.g. the default value
  -				// for the property.
  -				// But for the time being, I just prefer to be aware of any
  -				// problems occuring here, most likely programming errors,
  -				// so I'll bail out.
  -				throw new Error("Bad property value."+e);
  -				// TODO: review this and possibly change to:
  -				// setEditorValue(i, descriptors[i].getValue(DEFAULT); 
  -			}
  -		}
  -    }
  -
  -	/**
  -	 * Find the index of the property of the given name.
  -	 * 
  -	 * @param name the name of the property
  -	 * @return the index of that property in the descriptors array, or -1 if 
  -	 * 			there's no property of this name.
  -	 */
  -	private int descriptorIndex(String name) //NOTUSED
  -	{
  -		for (int i=0; i<descriptors.length; i++)
  -		{
  -			if (descriptors[i].getName().equals(name))
  -			{
  -				return i;
  -			}
  -		}
  -		return -1;
  -	}
  -	
       public TestElement createTestElement()
       {
           try
           {
               TestElement element= (TestElement)testBeanClass.newInstance();
  -            modifyTestElement(element);
  +            configure(element);
  +            super.clear(); // set name, enabled.
  +            configureTestElement(element);
  +            modifyTestElement(element); // put the default values back into the new element
               return element;
           }
           catch (InstantiationException e)
  @@ -551,93 +267,125 @@
        */
       public void modifyTestElement(TestElement element)
       {
  -        configureTestElement(element);
  -        for (int i=0; i<editors.length; i++)
  -        {
  -            if (editors[i] == null) continue;
  -            Object value= editors[i].getValue();
  -            
  -			if (value == null)
  -			{
  -				element.removeProperty(descriptors[i].getName());
  -			}
  -			else {
  -				JMeterProperty jprop= wrapInProperty(value);
  -				jprop.setName(descriptors[i].getName());
  -				element.setProperty(jprop);
  -			}
  +        super.configureTestElement(element);
  +        
  +        // Copy all property values from the map into the element:
  +        PropertyDescriptor[] props= beanInfo.getPropertyDescriptors();
  +        for (int i=0; i<props.length; i++)
  +        {
  +            String name= props[i].getName();
  +            JMeterProperty jprop= wrapInProperty(propertyMap.get(name));
  +            jprop.setName(name);
  +            element.setProperty(jprop);
           }
       }
   
  -	/**
  -	 * Utility method to wrap an object in a property of an appropriate type.
  -	 * <p>
  -	 * I plan to get rid of this sooner than later, so please don't use it much.
  -	 * 
  -	 * @param value Object to be wrapped.
  -	 * @return an unnamed property holding the provided value.
  -	 * @deprecated
  -	 */
  -	private static JMeterProperty wrapInProperty(Object value)
  -	{
  -		// TODO: Awful, again...
  +    /**
  +     * Utility method to wrap an object in a property of an appropriate type.
  +     * <p>
  +     * I plan to get rid of this sooner than later, so please don't use it much.
  +     * 
  +     * @param value Object to be wrapped.
  +     * @return an unnamed property holding the provided value.
  +     * @deprecated
  +     */
  +    private static JMeterProperty wrapInProperty(Object value)
  +    {
  +        // TODO: Awful, again...
           
  -		if (value instanceof JMeterProperty)
  -		{
  -			return (JMeterProperty)value;
  -		}
  +        if (value instanceof JMeterProperty)
  +        {
  +            return (JMeterProperty)value;
  +        }
           
  -		JMeterProperty property;
  -		if (value == null)
  -		{
  -			property= new NullProperty();
  -		}
  -		else if (value instanceof Boolean)
  -		{
  -			property= new BooleanProperty();
  -		}
  -		else if (value instanceof Double)
  -		{
  -			property= new DoubleProperty();
  -		}
  -		else if (value instanceof Float)
  -		{
  -			property= new FloatProperty();
  -		}
  -		else if (value instanceof Integer)
  -		{
  -			property= new IntegerProperty();
  -		}
  -		else if (value instanceof Long)
  -		{
  -			property= new LongProperty();
  -		}
  -		else if (value instanceof String)
  -		{
  -			property= new StringProperty();
  -		}
  -		else if (value instanceof TestElement)
  -		{
  -			property= new TestElementProperty();
  -		}
  -		else throw new Error("Ouch!");
  +        JMeterProperty property;
  +        if (value == null)
  +        {
  +            property= new NullProperty();
  +        }
  +        else if (value instanceof Boolean)
  +        {
  +            property= new BooleanProperty();
  +        }
  +        else if (value instanceof Double)
  +        {
  +            property= new DoubleProperty();
  +        }
  +        else if (value instanceof Float)
  +        {
  +            property= new FloatProperty();
  +        }
  +        else if (value instanceof Integer)
  +        {
  +            property= new IntegerProperty();
  +        }
  +        else if (value instanceof Long)
  +        {
  +            property= new LongProperty();
  +        }
  +        else if (value instanceof String)
  +        {
  +            property= new StringProperty();
  +        }
  +        else if (value instanceof TestElement)
  +        {
  +            property= new TestElementProperty();
  +        }
  +        else throw new Error("Ouch!");
   
  -		property.setObjectValue(value);
  +        property.setObjectValue(value);
   
  -		return property;
  -	}
  +        return property;
  +    }
   
       /* (non-Javadoc)
        * @see org.apache.jmeter.gui.JMeterGUIComponent#createPopupMenu()
        */
       public JPopupMenu createPopupMenu()
       {
  -    	// TODO: this menu is too wide (allows, e.g. to add controllers, no matter the
  -    	// type of the element). Change to match the actual bean's capabilities.
  +        // TODO: this menu is too wide (allows, e.g. to add controllers, no matter the
  +        // type of the element). Change to match the actual bean's capabilities.
           return MenuFactory.getDefaultControllerMenu();
       }
   
       /* (non-Javadoc)
  +     * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(org.apache.jmeter.testelement.TestElement)
  +     */
  +    public void configure(TestElement element)
  +    {
  +        if (! initialized) init();
  +
  +        super.configure(element);
  +
  +        // Copy all property values into the map:
  +        propertyMap.clear();
  +        for (PropertyIterator jprops= element.propertyIterator(); jprops.hasNext(); )
  +        {
  +            JMeterProperty jprop= jprops.next();
  +            propertyMap.put(jprop.getName(), jprop.getObjectValue());
  +        }
  +
  +        if (customizer != null)
  +        {
  +            customizer.setObject(propertyMap);
  +        }
  +        else
  +        {
  +            if (initialized) remove(customizerIndexInPanel);
  +            Customizer c= (Customizer)customizers.get(element);
  +            if (c == null)
  +            {
  +                c= createCustomizer();
  +                c.setObject(propertyMap);
  +                customizers.put(element, c);
  +            }
  +            add((Component)c, BorderLayout.CENTER);
  +        }
  +
  +        initialized= true;
  +    }
  +
  +    /* (non-Javadoc)
        * @see org.apache.jmeter.gui.JMeterGUIComponent#getMenuCategories()
        */
       public Collection getMenuCategories()
  @@ -679,225 +427,25 @@
           }
           return menuCategories;
       }
  -
  -    /**
  -     * Initialize the GUI.
  -     */
  +    
       private void init()
       {
  -		// TODO: add support for Bean Customizers
  -
           setLayout(new BorderLayout(0, 5));
   
           setBorder(makeBorder());
           add(makeTitlePanel(), BorderLayout.NORTH);
   
  -        JPanel mainPanel = new JPanel(new GridBagLayout());
  -        
  -        GridBagConstraints cl= new GridBagConstraints(); // for labels
  -		cl.gridx= 0;
  -		cl.anchor= GridBagConstraints.EAST;//JDK1.4: was LINE_END
  -		cl.insets= new Insets(0, 1, 0, 1);
  -
  -		GridBagConstraints ce= new GridBagConstraints(); // for editors
  -		ce.fill= GridBagConstraints.BOTH;
  -		ce.gridx= 1;
  -		ce.weightx= 1.0;
  -		ce.insets= new Insets(0, 1, 0, 1);
  -		
  -		GridBagConstraints cp= new GridBagConstraints(); // for panels
  -		cp.fill= GridBagConstraints.BOTH;
  -		cp.gridx= 1;
  -		cp.gridy= GridBagConstraints.RELATIVE;
  -		cp.gridwidth= 2;
  -		cp.weightx= 1.0;
  -
  -		JPanel currentPanel= mainPanel;
  -		String currentGroup= DEFAULT_GROUP;
  -		int y=0;
  -		
  -        for (int i=0; i<editors.length; i++)
  -        {
  -            if (editors[i] == null) continue;
  -
  -			if (log.isDebugEnabled())
  -			{
  -				log.debug("Laying property "+descriptors[i].getName());
  -			}
  -			
  -			String g= group(descriptors[i]);
  -			if (! currentGroup.equals(g))
  -			{
  -				if (currentPanel != mainPanel)
  -				{
  -					mainPanel.add(currentPanel, cp);
  -				}
  -				currentGroup= g;
  -				currentPanel= new JPanel(new GridBagLayout()); 
  -				currentPanel.setBorder(
  -					BorderFactory.createTitledBorder(
  -						BorderFactory.createEtchedBorder(),
  -						groupDisplayName(g)));
  -				cp.weighty= 0.0;
  -				y= 0;
  -			}
  -
  -			Component customEditor= editors[i].getCustomEditor();
  -
  -			boolean multiLineEditor= false;
  -			if (customEditor.getPreferredSize().height > 50)
  -			{
  -				// TODO: the above works in the current situation, but it's
  -				// just a hack. How to get each editor to report whether it
  -				// wants to grow bigger? Whether the property label should
  -				// be at the left or at the top of the editor? ...?
  -				multiLineEditor= true;
  -			}
  -			
  -			JLabel label= createLabel(descriptors[i]);
  -			label.setLabelFor(customEditor);
  -
  -			cl.gridy= y;
  -			cl.gridwidth= multiLineEditor ? 2 : 1;
  -			cl.anchor= multiLineEditor 
  -				? GridBagConstraints.CENTER
  -				: GridBagConstraints.EAST;//JDK1.4: was LINE_END
  -            currentPanel.add(label, cl);
  -
  -			ce.gridx= multiLineEditor ? 0 : 1;
  -			ce.gridy= multiLineEditor ? ++y : y;
  -			ce.gridwidth= multiLineEditor ? 2 : 1;
  -			ce.weighty= multiLineEditor ? 1.0 : 0.0;
  -
  -			cp.weighty+= ce.weighty;
  -
  -            currentPanel.add(customEditor, ce);
  -
  -            y++;
  -        }
  -		if (currentPanel != mainPanel)
  -		{
  -			mainPanel.add(currentPanel, cp);
  -		}
  -		
  -		// Add a 0-sized invisible component that will take all the vertical
  -		// space that nobody wants:
  -		GridBagConstraints cs= new GridBagConstraints(); // for strut
  -		cs.gridx= 1;
  -		cs.gridy= y++;
  -		cs.gridwidth= 2;
  -		cs.weighty= 0.0001;
  -
  -		mainPanel.add(Box.createHorizontalStrut(0), cs);
  -		
  -		// Done: add the panel to the GUI:
  -        add(mainPanel, BorderLayout.CENTER);
  -    }
  +        customizerIndexInPanel= getComponentCount();
   
  -	private JLabel createLabel(PropertyDescriptor desc)
  -	{
  -		String text= desc.getDisplayName();
  -		if (! "".equals(text))
  -		{
  -			text= propertyFieldLabelMessage.format(
  -				new Object[] { desc.getDisplayName() } );
  -		}
  -		// if the displayName is the empty string, leave it like that.
  -		JLabel label = new JLabel(text);
  -		label.setHorizontalAlignment(JLabel.TRAILING);
  -		text= propertyToolTipMessage.format(
  -			new Object[] { desc.getName(), desc.getShortDescription() } );
  -		label.setToolTipText(text);
  -
  -		return label;
  -	}
  -
  -
  -	/**
  -	 * Obtain a property descriptor's group.
  -	 * 
  -	 * @param descriptor
  -	 * @return the group String.
  -	 */
  -	private String group(PropertyDescriptor d)
  -	{
  -		String group= (String)d.getValue(GROUP);
  -		if (group == null) group= DEFAULT_GROUP;
  -		return group;
  -	}
  -
  -	/**
  -	 *  Obtain a group's display name
  -	 */
  -	private String groupDisplayName(String group)
  -	{
  -		try {
  -			ResourceBundle b= (ResourceBundle)
  -				beanInfo.getBeanDescriptor().getValue(RESOURCE_BUNDLE);
  -			if (b == null) return group;
  -			else return b.getString(group+".displayName");
  -		}
  -		catch (MissingResourceException e)
  -		{
  -			return group;
  -		}
  -	}
  -
  -    /**
  -     * Comparator used to sort properties for presentation in the GUI.
  -     */
  -    private class PropertyComparator implements Comparator
  -    {
  -		public int compare(Object o1, Object o2)
  -		{
  -			return compare((PropertyDescriptor)o1, (PropertyDescriptor)o2);
  -		}
  -		
  -		private int compare(PropertyDescriptor d1, PropertyDescriptor d2)
  -		{
  -			int result;
  -		
  -			String g1= group(d1), g2= group(d2);
  -			Integer go1= groupOrder(g1), go2= groupOrder(g2);
  -		
  -			result= go1.compareTo(go2);
  -			if (result != 0) return result;
  -		
  -			result= g1.compareTo(g2);
  -			if (result != 0) return result;
  -		
  -			Integer po1= propertyOrder(d1), po2= propertyOrder(d2);
  -			result= po1.compareTo(po2);
  -			if (result != 0) return result;
  -		
  -			return d1.getName().compareTo(d2.getName());
  -		}
  -	
  -		/**
  -		 * Obtain a group's order.
  -		 * 
  -		 * @param group group name
  -		 * @return the group's order (zero by default)
  -		 */
  -		private Integer groupOrder(String group)
  -		{
  -			Integer order= (Integer)beanInfo.getBeanDescriptor()
  -					.getValue(ORDER(group));
  -			if (order == null) order= new Integer(0);
  -			return order;
  -		}
  -
  -		/**
  -		 * Obtain a property's order.
  -		 * 
  -		 * @param d
  -		 * @return the property's order attribute (zero by default)
  -		 */
  -		private Integer propertyOrder(PropertyDescriptor d)
  -		{
  -			Integer order= (Integer)d.getValue(ORDER);
  -			if (order == null) order= new Integer(0);
  -			return order;
  -		}
  +        if (customizerClass == null)
  +        {
  +            customizer= new GenericTestBeanCustomizer(beanInfo);
  +        }
  +        else if (SharedCustomizer.class.isAssignableFrom(customizerClass))
  +        {
  +            customizer= createCustomizer();
  +        }
  +        
  +        if (customizer != null) add((Component)customizer, BorderLayout.CENTER);
       }
   }
  
  
  
  1.3       +9 -6      jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/TestElementEditor.java
  
  Index: TestElementEditor.java
  ===================================================================
  RCS file: /home/cvs/jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/TestElementEditor.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- TestElementEditor.java	14 Jan 2004 23:10:30 -0000	1.2
  +++ TestElementEditor.java	28 Jan 2004 10:35:14 -0000	1.3
  @@ -71,9 +71,10 @@
    */
   public abstract class TestElementEditor extends PropertyEditorSupport {
   
  -	Class guiClass;
  -	JMeterGUIComponent guiComponent;
  -	
  +	private Class guiClass;
  +    private JMeterGUIComponent guiComponent;
  +    private TestElement element;
  +    
   	/**
   	 * Create a property editor from a given Component subclass implementing
   	 * JMeterGuiComponent -- most often an AbstractJMeterGuiComponent subclass.
  @@ -99,7 +100,8 @@
   	 * @see java.beans.PropertyEditor#getValue()
   	 */
   	public Object getValue() {
  -		return guiComponent.createTestElement();
  +		guiComponent.modifyTestElement(element);
  +        return element;
   	}
   
   	/**
  @@ -109,7 +111,8 @@
   	public void setValue(Object value) {
   		if (value != null)
   		{
  -			guiComponent.configure((TestElement)value);
  +            element= (TestElement)value;
  +			guiComponent.configure(element);
   			firePropertyChange();
   		}
   	}
  
  
  
  1.5       +18 -11    jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/BeanInfoSupport.java
  
  Index: BeanInfoSupport.java
  ===================================================================
  RCS file: /home/cvs/jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/BeanInfoSupport.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- BeanInfoSupport.java	16 Jan 2004 02:28:08 -0000	1.4
  +++ BeanInfoSupport.java	28 Jan 2004 10:35:15 -0000	1.5
  @@ -103,13 +103,19 @@
   
   	private static transient Logger log = LoggingManager.getLoggerForClass();
   
  -	// Some known attribute names, just for convenience:
  -	public static final String TAGS= TestBeanGUI.TAGS;
  -	public static final String NOT_UNDEFINED= TestBeanGUI.NOT_UNDEFINED;
  -	public static final String NOT_EXPRESSION= TestBeanGUI.NOT_EXPRESSION;
  -	public static final String NOT_OTHER= TestBeanGUI.NOT_OTHER;
  -	public static final String DEFAULT= TestBeanGUI.DEFAULT;
  -	public static final String RESOURCE_BUNDLE= TestBeanGUI.RESOURCE_BUNDLE;
  +    // Some known attribute names, just for convenience:
  +    public static final String TAGS=
  +            GenericTestBeanCustomizer.TAGS;
  +    public static final String NOT_UNDEFINED=
  +            GenericTestBeanCustomizer.NOT_UNDEFINED;
  +    public static final String NOT_EXPRESSION=
  +            GenericTestBeanCustomizer.NOT_EXPRESSION;
  +    public static final String NOT_OTHER=
  +            GenericTestBeanCustomizer.NOT_OTHER;
  +    public static final String DEFAULT=
  +            GenericTestBeanCustomizer.DEFAULT;
  +    public static final String RESOURCE_BUNDLE=
  +            GenericTestBeanCustomizer.RESOURCE_BUNDLE;
   
   	/**
   	 * The class for which we're providing the bean info.
  @@ -233,11 +239,12 @@
   		for (int i=0; i<names.length; i++)
   		{
   			PropertyDescriptor p= property(names[i]);
  -			p.setValue(TestBeanGUI.GROUP, group);
  -			p.setValue(TestBeanGUI.ORDER, new Integer(i));
  +			p.setValue(GenericTestBeanCustomizer.GROUP, group);
  +			p.setValue(GenericTestBeanCustomizer.ORDER, new Integer(i));
   		}
   		numCreatedGroups++;
  -		getBeanDescriptor().setValue(TestBeanGUI.ORDER(group),
  +		getBeanDescriptor().setValue(
  +            GenericTestBeanCustomizer.ORDER(group),
   			new Integer(numCreatedGroups));
   	}
   
  
  
  
  1.6       +1 -16     jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/WrapperEditor.java
  
  Index: WrapperEditor.java
  ===================================================================
  RCS file: /home/cvs/jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/WrapperEditor.java,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- WrapperEditor.java	27 Jan 2004 13:22:24 -0000	1.5
  +++ WrapperEditor.java	28 Jan 2004 10:35:16 -0000	1.6
  @@ -186,21 +186,6 @@
               acceptsOther, defaultValue);
       }
   
  -    /**
  -     * Constructor for backward compatibility -- will soon be removed.
  -     */
  -    WrapperEditor(
  -            PropertyEditor typeEditor, 
  -            PropertyEditor guiEditor,
  -            boolean acceptsNull, 
  -            boolean acceptsExpressions, 
  -            boolean acceptsOther)
  -    {
  -        super();
  -        initialize(typeEditor, guiEditor, acceptsNull, acceptsExpressions,
  -            acceptsOther, null);
  -    }
  -    
       private void initialize(
               PropertyEditor typeEditor, 
               PropertyEditor guiEditor,
  
  
  
  1.1                  jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/GenericTestBeanCustomizer.java
  
  Index: GenericTestBeanCustomizer.java
  ===================================================================
  /*
   * ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2003 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   * notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   * notice, this list of conditions and the following disclaimer in
   * the documentation and/or other materials provided with the
   * distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   * if any, must include the following acknowledgment:
   * "This product includes software developed by the
   * Apache Software Foundation (http://www.apache.org/)."
   * Alternately, this acknowledgment may appear in the software itself,
   * if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and
   * "Apache JMeter" must not be used to endorse or promote products
   * derived from this software without prior written permission. For
   * written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   * "Apache JMeter", nor may "Apache" appear in their name, without
   * prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   * 
   * @author <a href="mailto:jsalvata@apache.org">Jordi Salvat i Alabart</a>
   * @version $Id: GenericTestBeanCustomizer.java,v 1.1 2004/01/28 10:35:12 jsalvata Exp $
   */
  package org.apache.jmeter.testbeans.gui;
  
  import java.awt.Component;
  import java.awt.GridBagConstraints;
  import java.awt.GridBagLayout;
  import java.awt.Insets;
  
  import java.beans.BeanInfo;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.beans.PropertyDescriptor;
  import java.beans.PropertyEditor;
  import java.beans.PropertyEditorManager;
  
  import java.text.MessageFormat;
  import java.util.Arrays;
  import java.util.Comparator;
  import java.util.Map;
  import java.util.MissingResourceException;
  import java.util.ResourceBundle;
  
  import javax.swing.BorderFactory;
  import javax.swing.Box;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  
  import org.apache.jmeter.util.JMeterUtils;
  import org.apache.jorphan.logging.LoggingManager;
  import org.apache.log.Logger;
  
  /**
   * The GenericTestBeanCustomizer is designed to provide developers with a
   * mechanism to quickly implement GUIs for new components.
   * <p>
   * It allows editing each of the public exposed properties of the
   * edited type 'a la JavaBeans': as far as the types of those properties
   * have an associated editor, there's no GUI development required. 
   * <p>
   * This class understands the following PropertyDescriptor attributes:
   * <dl>
   * <dt>group: String</dt>
   * <dd>Group under which the property should be shown in the GUI. The string is
   * also used as a group title (but see comment on resourceBundle below). The
   * default group is "".</dd>
   * <dt>order: Integer</dt>
   * <dd>Order in which the property will be shown in its group. A smaller
   * integer means higher up in the GUI. The default order is 0. Properties
   * of equal order are sorted alphabetically.</dd>
   * <dt>tags: String[]</dt>
   * <dd>List of values to be offered for the property in addition to those
   * offered by its property editor.</dd>
   * <dt>notUndefined: Boolean</dt>
   * <dd>If true, the property should not be left undefined. A <b>default</b>
   * attribute must be provided if this is set.</dd>
   * <dd>notExpression: Boolean</dd>
   * <dd>If true, the property content should always be constant: JMeter
   * 'expressions' (strings using ${var}, etc...) can't be used.</dt>
   * <dd>notOther: Boolean</dd>
   * <dd>If true, the property content must always be one of the tags values or
   * null.</dt>
   * <dt>default: Object</dt>
   * <dd>Initial value for the property's GUI. Must be provided and be non-null
   * if <b>notUndefined</b> is set. Must be one of the provided tags (or null) if
   * <b>notOther</b> is set.
   * </dl>
   * <p>
   * The following BeanDescriptor attributes are also understood:
   * <dl>
   * <dt>group.<i>group</i>.order: Integer</dt>
   * <dd>where <b><i>group</i></b> is a group name used in a <b>group</b>
   * attribute in one or more PropertyDescriptors. Defines the order in which
   * the group will be shown in the GUI. A smaller integer means higher up
   * in the GUI. The default order is 0. Groups of equal order are sorted
   * alphabetically.</dd>
   * <dt>resourceBundle: ResourceBundle</dt>
   * <dd>A resource bundle to be used for GUI localization: group display names
   * will be obtained from property "<b><i>group</i>.displayName</b>" if
   * available (where <b><i>group</i></b> is the group name).
   * </dl>
   */
  class GenericTestBeanCustomizer extends JPanel
          implements SharedCustomizer, PropertyChangeListener 
  {
      private static Logger log = LoggingManager.getLoggerForClass();
  
  	public static final String GROUP= "group";
  	public static final String ORDER= "order";
  	public static final String TAGS= "tags";
  	public static final String NOT_UNDEFINED= "notUndefined";
  	public static final String NOT_EXPRESSION= "notExpression";
  	public static final String NOT_OTHER= "notOther";
  	public static final String DEFAULT= "default";
  	public static final String RESOURCE_BUNDLE= "resourceBundle";
  	public static final String ORDER(String group) {
  		return "group."+group+".order";
  	}
  
  	public static final String DEFAULT_GROUP= "";
  
      /**
       * BeanInfo object for the class of the objects being edited.
       */
      private BeanInfo beanInfo;
  
      /**
       * Property descriptors from the beanInfo.
       */
      private PropertyDescriptor[] descriptors;
  
      /**
       * Property editors -- or null if the property can't be edited.
       * Unused if customizerClass==null.
       */
      private PropertyEditor[] editors;
  
  	/**
  	 * Message format for property field labels:
  	 */
  	private MessageFormat propertyFieldLabelMessage;
  	
  	/**
  	 * Message format for property tooltips:
  	 */
  	private MessageFormat propertyToolTipMessage;
  
      /**
       * The Map we're currently customizing. Set by setObject().
       */
      private Map propertyMap;
  
      /**
       * Create a customizer for a given test bean type.
       * 
       * @param testBeanClass a subclass of TestBean
       * @see org.apache.jmeter.testbeans.TestBean 
       */
      GenericTestBeanCustomizer(BeanInfo beanInfo)
      {
          super();
  
          this.beanInfo= beanInfo;
          
          // Get and sort the property descriptors:
          descriptors= beanInfo.getPropertyDescriptors();
  		Arrays.sort(descriptors, new PropertyComparator());
  
          // Obtain the propertyEditors:
          editors= new PropertyEditor[descriptors.length];
          for (int i=0; i<descriptors.length; i++)
          {
              String name= descriptors[i].getName();
  
              // Don't get editors for hidden or non-read-write properties:
              if (descriptors[i].isHidden()
                  || descriptors[i].getReadMethod() == null
                  || descriptors[i].getWriteMethod() == null)
              {
                  log.debug("No editor for property "+name);
                  editors[i]= null;
                  continue;
              }
  
              PropertyEditor propertyEditor;
              Class editorClass= descriptors[i].getPropertyEditorClass();
              
              if (log.isDebugEnabled())
              {
                  log.debug("Property "+name
                          +" has editor class "+editorClass);
              }
              
              if (editorClass != null)
              {
                  try
                  {
                      propertyEditor= (PropertyEditor)editorClass.newInstance();
                  }
                  catch (InstantiationException e)
                  {
                      log.error("Can't create property editor.", e);
                      throw new Error(e.toString());
                  }
                  catch (IllegalAccessException e)
                  {
                      log.error("Can't create property editor.", e);
                      throw new Error(e.toString());
                  }
              }
              else
              {
                  Class c= descriptors[i].getPropertyType();
                  propertyEditor= PropertyEditorManager.findEditor(c);
              }
  
              if (log.isDebugEnabled())
              {
                  log.debug("Property "+name
                          +" has property editor "+propertyEditor);
              }
              
              if (propertyEditor == null)
              {
                  log.debug("No editor for property "+name);
                  editors[i]= null;
                  continue;
              }
              
              if (! propertyEditor.supportsCustomEditor())
              {
  				propertyEditor= createWrapperEditor(
  					propertyEditor, descriptors[i]);
  
  				if (log.isDebugEnabled())
  				{
  					log.debug("Editor for property "+name
  							+" is wrapped in "+propertyEditor);
  				}
              }
              
  			editors[i]= propertyEditor;
  
  			// Initialize the editor with the provided default value or null:
              setEditorValue(i, descriptors[i].getValue(DEFAULT));
  
              // Now subscribe as a listener (we didn't want to receive the event
              // for the setEditorValue above!)
              propertyEditor.addPropertyChangeListener(this);
          }
  
  		// Obtain message formats:
  		propertyFieldLabelMessage= new MessageFormat(	
  			JMeterUtils.getResString("property_as_field_label"));
  		propertyToolTipMessage= new MessageFormat(	
  			JMeterUtils.getResString("property_tool_tip"));
  
          // Initialize the GUI:
          init();
      }
  
  	/**
  	 * Find the default typeEditor and a suitable guiEditor for the given
  	 * property descriptor, and combine them in a WrapperEditor.
  	 * 
  	 * @param typeEditor
  	 * @param descriptor
  	 * @return
  	 */
  	private WrapperEditor createWrapperEditor(
  			PropertyEditor typeEditor, PropertyDescriptor descriptor)
  	{
  		String[] editorTags= typeEditor.getTags();
  		String[] additionalTags= (String[])descriptor.getValue(TAGS);
  		String[] tags= null;
  		if (editorTags == null) tags= additionalTags;
  		else if (additionalTags == null) tags= editorTags;
  		else {
  			tags= new String[editorTags.length+additionalTags.length];
  			int j= 0;
  			for (int i=0; i<editorTags.length; i++) tags[j++]= editorTags[i];
  			for (int i=0; i<additionalTags.length; i++) tags[j++]= additionalTags[i];
  		}
  
  		boolean notNull=
  			Boolean.TRUE.equals(descriptor.getValue(NOT_UNDEFINED));
  		boolean notExpression=
  			Boolean.TRUE.equals(descriptor.getValue(NOT_EXPRESSION));
  		boolean notOther=
  			Boolean.TRUE.equals(descriptor.getValue(NOT_OTHER));
  
  		PropertyEditor guiEditor;
  		if (notNull && tags==null)
  		{
  			guiEditor= new FieldStringEditor();
  		}
  		else
  		{
  			ComboStringEditor e= new ComboStringEditor();
  			e.setNoUndefined(notNull);
  			e.setNoEdit(notExpression && notOther);
  			e.setTags(tags);
  			
  			guiEditor= e;
  		}
  
  		WrapperEditor wrapper= new WrapperEditor(
  			typeEditor, guiEditor,
  			!notNull, // acceptsNull
  			!notExpression, // acceptsExpressions
  			!notOther, // acceptsOther
              descriptor.getValue(DEFAULT)
  			);
  
  		return wrapper;
  	}
  
  	/**
  	 * Set the value of the i-th property, properly reporting a possible failure.
  	 * 
  	 * @param i the index of the property in the descriptors and editors arrays
  	 * @param value the value to be stored in the editor
  	 * 
  	 * @throws IllegalArgumentException if the editor refuses the value
  	 */
  	private void setEditorValue(int i, Object value)
  		throws IllegalArgumentException
  	{
  		try
  		{
  			editors[i].setValue(value);
  		}
  		catch (IllegalArgumentException e)
  		{
              log.error("Could not set value "
                  + ( value == null ? "NULL" : value.getClass().getName() )
                  + ":" + value
                  +" for property "+descriptors[i].getName());
  			throw e;
  		}
  	}
  
      /* (non-Javadoc)
       * @see org.apache.jmeter.gui.JMeterGUIComponent#configure(org.apache.jmeter.testelement.TestElement)
       */
      public void setObject(Object map)
      {
          propertyMap= (Map)map;
  
          if (propertyMap.size() == 0)
          {
              // Uninitialized -- set it to the defaults:
              for (int i=0; i<editors.length; i++)
              {
                  Object value= descriptors[i].getValue(DEFAULT);
                  String name= descriptors[i].getName();
                  if (value != null)
                  {
                      propertyMap.put(name, value);
                      log.debug("Set "+name+"= "+value);
                  }
                  firePropertyChange(name, null, value);
              }
          }
          
          // Now set the editors to the element's values:
          for (int i=0; i<editors.length; i++)
          {
              if (editors[i] == null) continue;
              try
              {
                  setEditorValue(i, propertyMap.get(descriptors[i].getName()));
              }
              catch (IllegalArgumentException e)
              {
                  // I guess this can happen as a result of a bad
                  // file read? In this case, it would be better to replace the
                  // incorrect value with anything valid, e.g. the default value
                  // for the property.
                  // But for the time being, I just prefer to be aware of any
                  // problems occuring here, most likely programming errors,
                  // so I'll bail out.
                  throw new Error("Bad property value."+e);
                  // TODO: review this and possibly change to:
                  // setEditorValue(i, descriptors[i].getValue(DEFAULT); 
              }
          }
      }
  
  	/**
  	 * Find the index of the property of the given name.
  	 * 
  	 * @param name the name of the property
  	 * @return the index of that property in the descriptors array, or -1 if 
  	 * 			there's no property of this name.
  	 */
  	private int descriptorIndex(String name) //NOTUSED
  	{
  		for (int i=0; i<descriptors.length; i++)
  		{
  			if (descriptors[i].getName().equals(name))
  			{
  				return i;
  			}
  		}
  		return -1;
  	}
  
      /**
       * Initialize the GUI.
       */
      private void init()
      {
          setLayout(new GridBagLayout());
          
          GridBagConstraints cl= new GridBagConstraints(); // for labels
  		cl.gridx= 0;
  		cl.anchor= GridBagConstraints.EAST;//JDK1.4: was LINE_END
  		cl.insets= new Insets(0, 1, 0, 1);
  
  		GridBagConstraints ce= new GridBagConstraints(); // for editors
  		ce.fill= GridBagConstraints.BOTH;
  		ce.gridx= 1;
  		ce.weightx= 1.0;
  		ce.insets= new Insets(0, 1, 0, 1);
  		
  		GridBagConstraints cp= new GridBagConstraints(); // for panels
  		cp.fill= GridBagConstraints.BOTH;
  		cp.gridx= 1;
  		cp.gridy= GridBagConstraints.RELATIVE;
  		cp.gridwidth= 2;
  		cp.weightx= 1.0;
  
  		JPanel currentPanel= this;
  		String currentGroup= DEFAULT_GROUP;
  		int y=0;
  		
          for (int i=0; i<editors.length; i++)
          {
              if (editors[i] == null) continue;
  
  			if (log.isDebugEnabled())
  			{
  				log.debug("Laying property "+descriptors[i].getName());
  			}
  			
  			String g= group(descriptors[i]);
  			if (! currentGroup.equals(g))
  			{
  				if (currentPanel != this)
  				{
  					add(currentPanel, cp);
  				}
  				currentGroup= g;
  				currentPanel= new JPanel(new GridBagLayout()); 
  				currentPanel.setBorder(
  					BorderFactory.createTitledBorder(
  						BorderFactory.createEtchedBorder(),
  						groupDisplayName(g)));
  				cp.weighty= 0.0;
  				y= 0;
  			}
  
  			Component customEditor= editors[i].getCustomEditor();
  
  			boolean multiLineEditor= false;
  			if (customEditor.getPreferredSize().height > 50)
  			{
  				// TODO: the above works in the current situation, but it's
  				// just a hack. How to get each editor to report whether it
  				// wants to grow bigger? Whether the property label should
  				// be at the left or at the top of the editor? ...?
  				multiLineEditor= true;
  			}
  			
  			JLabel label= createLabel(descriptors[i]);
  			label.setLabelFor(customEditor);
  
  			cl.gridy= y;
  			cl.gridwidth= multiLineEditor ? 2 : 1;
  			cl.anchor= multiLineEditor 
  				? GridBagConstraints.CENTER
  				: GridBagConstraints.EAST;//JDK1.4: was LINE_END
              currentPanel.add(label, cl);
  
  			ce.gridx= multiLineEditor ? 0 : 1;
  			ce.gridy= multiLineEditor ? ++y : y;
  			ce.gridwidth= multiLineEditor ? 2 : 1;
  			ce.weighty= multiLineEditor ? 1.0 : 0.0;
  
  			cp.weighty+= ce.weighty;
  
              currentPanel.add(customEditor, ce);
  
              y++;
          }
  		if (currentPanel != this)
  		{
  			add(currentPanel, cp);
  		}
  		
  		// Add a 0-sized invisible component that will take all the vertical
  		// space that nobody wants:
  		GridBagConstraints cs= new GridBagConstraints(); // for strut
  		cs.gridx= 1;
  		cs.gridy= y++;
  		cs.gridwidth= 2;
  		cs.weighty= 0.0001;
  
  		add(Box.createHorizontalStrut(0), cs);
      }
  
  	private JLabel createLabel(PropertyDescriptor desc)
  	{
  		String text= desc.getDisplayName();
  		if (! "".equals(text))
  		{
  			text= propertyFieldLabelMessage.format(
  				new Object[] { desc.getDisplayName() } );
  		}
  		// if the displayName is the empty string, leave it like that.
  		JLabel label = new JLabel(text);
  		label.setHorizontalAlignment(JLabel.TRAILING);
  		text= propertyToolTipMessage.format(
  			new Object[] { desc.getName(), desc.getShortDescription() } );
  		label.setToolTipText(text);
  
  		return label;
  	}
  
  
  	/**
  	 * Obtain a property descriptor's group.
  	 * 
  	 * @param descriptor
  	 * @return the group String.
  	 */
  	private String group(PropertyDescriptor d)
  	{
  		String group= (String)d.getValue(GROUP);
  		if (group == null) group= DEFAULT_GROUP;
  		return group;
  	}
  
  	/**
  	 *  Obtain a group's display name
  	 */
  	private String groupDisplayName(String group)
  	{
  		try {
  			ResourceBundle b= (ResourceBundle)
  				beanInfo.getBeanDescriptor().getValue(RESOURCE_BUNDLE);
  			if (b == null) return group;
  			else return b.getString(group+".displayName");
  		}
  		catch (MissingResourceException e)
  		{
  			return group;
  		}
  	}
  
      /**
       * Comparator used to sort properties for presentation in the GUI.
       */
      private class PropertyComparator implements Comparator
      {
  		public int compare(Object o1, Object o2)
  		{
  			return compare((PropertyDescriptor)o1, (PropertyDescriptor)o2);
  		}
  		
  		private int compare(PropertyDescriptor d1, PropertyDescriptor d2)
  		{
  			int result;
  		
  			String g1= group(d1), g2= group(d2);
  			Integer go1= groupOrder(g1), go2= groupOrder(g2);
  		
  			result= go1.compareTo(go2);
  			if (result != 0) return result;
  		
  			result= g1.compareTo(g2);
  			if (result != 0) return result;
  		
  			Integer po1= propertyOrder(d1), po2= propertyOrder(d2);
  			result= po1.compareTo(po2);
  			if (result != 0) return result;
  		
  			return d1.getName().compareTo(d2.getName());
  		}
  	
  		/**
  		 * Obtain a group's order.
  		 * 
  		 * @param group group name
  		 * @return the group's order (zero by default)
  		 */
  		private Integer groupOrder(String group)
  		{
  			Integer order= (Integer)beanInfo.getBeanDescriptor()
  					.getValue(ORDER(group));
  			if (order == null) order= new Integer(0);
  			return order;
  		}
  
  		/**
  		 * Obtain a property's order.
  		 * 
  		 * @param d
  		 * @return the property's order attribute (zero by default)
  		 */
  		private Integer propertyOrder(PropertyDescriptor d)
  		{
  			Integer order= (Integer)d.getValue(ORDER);
  			if (order == null) order= new Integer(0);
  			return order;
  		}
      }
  
      /* (non-Javadoc)
       * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
       */
      public void propertyChange(PropertyChangeEvent evt)
      {
          for (int i=0; i<editors.length; i++)
          {
              if (editors[i] == evt.getSource())
              {
                  Object value= editors[i].getValue();
                  String name= descriptors[i].getName();
                  if (value == null)
                  {
                      propertyMap.remove(name);
                      log.debug("Unset "+name);
                  }
                  else {
                      propertyMap.put(name, value);
                      log.debug("Set "+name+"= "+value);
                  }
                  firePropertyChange(name, evt.getOldValue(), value);
                  return;
              }
          }
          throw new Error("Unexpected propertyChange event received: "+evt);
      }
  }
  
  
  
  1.1                  jakarta-jmeter/src/core/org/apache/jmeter/testbeans/gui/SharedCustomizer.java
  
  Index: SharedCustomizer.java
  ===================================================================
  /*
   * ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2004 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   * notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   * notice, this list of conditions and the following disclaimer in
   * the documentation and/or other materials provided with the
   * distribution.
   *
   * 3. The end-user documentation included with the redistribution,
   * if any, must include the following acknowledgment:
   * "This product includes software developed by the
   * Apache Software Foundation (http://www.apache.org/)."
   * Alternately, this acknowledgment may appear in the software itself,
   * if and wherever such third-party acknowledgments normally appear.
   *
   * 4. The names "Apache" and "Apache Software Foundation" and
   * "Apache JMeter" must not be used to endorse or promote products
   * derived from this software without prior written permission. For
   * written permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache",
   * "Apache JMeter", nor may "Apache" appear in their name, without
   * prior written permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   * 
   * @author <a href="mailto:jsalvata@apache.org">Jordi Salvat i Alabart</a>
   * @version $Id: SharedCustomizer.java,v 1.1 2004/01/28 10:35:17 jsalvata Exp $
   */
  package org.apache.jmeter.testbeans.gui;
  
  import java.beans.Customizer;
  
  /**
   * Tagging interface to mark a customizer class as shareable among elements
   * of the same type.
   * <p>
   * The interface is equivalent to Customizer -- the only difference is that setElement
   * can be called multiple times to change the element it works on.
   */
  public interface SharedCustomizer extends Customizer
  {
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: jmeter-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: jmeter-dev-help@jakarta.apache.org