Logo Search packages:      
Sourcecode: jakarta-jmeter version File versions  Download package

GenericTestBeanCustomizer.java

/*
 * 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.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.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.io.Serializable;
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 javax.swing.JScrollPane;

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>
 */
00094 public class GenericTestBeanCustomizer extends JPanel implements SharedCustomizer {
    private static final Logger log = LoggingManager.getLoggerForClass();

    public static final String GROUP = "group"; //$NON-NLS-1$

    public static final String ORDER = "order"; //$NON-NLS-1$

    public static final String TAGS = "tags"; //$NON-NLS-1$

    public static final String NOT_UNDEFINED = "notUndefined"; //$NON-NLS-1$

    public static final String NOT_EXPRESSION = "notExpression"; //$NON-NLS-1$

    public static final String NOT_OTHER = "notOther"; //$NON-NLS-1$

    public static final String DEFAULT = "default"; //$NON-NLS-1$

    public static final String RESOURCE_BUNDLE = "resourceBundle"; //$NON-NLS-1$

    public static final String ORDER(String group) {
        return "group." + group + ".order";
    }

    public static final String DEFAULT_GROUP = "";

    private int scrollerCount = 0;

    /**
     * BeanInfo object for the class of the objects being edited.
     */
00124     private transient BeanInfo beanInfo;

    /**
     * Property descriptors from the beanInfo.
     */
00129     private transient PropertyDescriptor[] descriptors;

    /**
     * Property editors -- or null if the property can't be edited. Unused if
     * customizerClass==null.
     */
00135     private transient PropertyEditor[] editors;

    /**
     * Message format for property field labels:
     */
00140     private MessageFormat propertyFieldLabelMessage;

    /**
     * Message format for property tooltips:
     */
00145     private MessageFormat propertyToolTipMessage;

    /**
     * The Map we're currently customizing. Set by setObject().
     */
00150     private Map propertyMap;

    public GenericTestBeanCustomizer(){
        log.warn("Constructor only intended for use in testing"); // $NON-NLS-1$
    }
    /**
     * Create a customizer for a given test bean type.
     *
     * @param testBeanClass
     *            a subclass of TestBean
     * @see org.apache.jmeter.testbeans.TestBean
     */
00162     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].isExpert() && !JMeterUtils.isExpertMode())
                    || 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);
                }
            }
            if (propertyEditor.getCustomEditor() instanceof JScrollPane) {
                scrollerCount++;
            }

            editors[i] = propertyEditor;

            // Initialize the editor with the provided default value or null:
            setEditorValue(i, descriptors[i].getValue(DEFAULT));

        }

        // Obtain message formats:
        propertyFieldLabelMessage = new MessageFormat(JMeterUtils.getResString("property_as_field_label")); //$NON-NLS-1$
        propertyToolTipMessage = new MessageFormat(JMeterUtils.getResString("property_tool_tip")); //$NON-NLS-1$

        // 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
     */
00250     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
     */
00306     private void setEditorValue(int i, Object value) throws IllegalArgumentException {
        editors[i].setValue(value);
    }

    /*
     * (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.
                // (MS Note) Can't bail out - newly create elements have blank
                // values and must get the defaults.
                // Also, when loading previous versions of jmeter test scripts,
                // some values
                // may not be right, and should get default values - MS
                // 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.
     */
00378     private void init() {
        setLayout(new GridBagLayout());

        GridBagConstraints cl = new GridBagConstraints(); // for labels
        cl.gridx = 0;
        cl.anchor = GridBagConstraints.EAST;
        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 || customEditor instanceof JScrollPane) {
                // 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;
            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:
        cp.weighty = 0.0001;
        add(Box.createHorizontalStrut(0), cp);
    }

    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.
     */
00485     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
     */
00496     private String groupDisplayName(String group) {
        try {
            ResourceBundle b = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(RESOURCE_BUNDLE);
            if (b == null) {
                return group;
            }
            return b.getString(group + ".displayName");
        } catch (MissingResourceException e) {
            return group;
        }
    }

    /**
     * Comparator used to sort properties for presentation in the GUI.
     */
00511     private class PropertyComparator implements Comparator, Serializable {
        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)
         */
00548         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)
         */
00562         private Integer propertyOrder(PropertyDescriptor d) {
            Integer order = (Integer) d.getValue(ORDER);
            if (order == null) {
                order = new Integer(0);
            }
            return order;
        }
    }

    /**
     * Save values from the GUI fields into the property map
     */
00574     void saveGuiFields() {
        for (int i = 0; i < editors.length; i++) {
            PropertyEditor propertyEditor=editors[i]; // might be null (e.g. in testing)
            if (propertyEditor != null) {
                Object value = propertyEditor.getValue();
                String name = descriptors[i].getName();
                if (value == null) {
                    propertyMap.remove(name);
                    if (log.isDebugEnabled()) {
                        log.debug("Unset " + name);
                    }
                } else {
                    propertyMap.put(name, value);
                    if (log.isDebugEnabled()) {
                        log.debug("Set " + name + "= " + value);
                    }
                }
            }
        }
    }

    void clearGuiFields() {
        for (int i = 0; i < editors.length; i++) {
            PropertyEditor propertyEditor=editors[i]; // might be null (e.g. in testing)
            if (propertyEditor != null) {
                try {
                if (propertyEditor instanceof WrapperEditor){
                    WrapperEditor we = (WrapperEditor) propertyEditor;
                    String tags[]=we.getTags();
                    if (tags != null && tags.length > 0) {
                        we.setAsText(tags[0]);
                    } else {
                        we.setValue("");
                    }
                } else if (propertyEditor instanceof ComboStringEditor) {
                    ComboStringEditor cse = (ComboStringEditor) propertyEditor;
                    cse.setAsText(cse.getInitialEditValue());
                } else {
                    propertyEditor.setAsText("");
                }
                } catch (IllegalArgumentException ex){
                    log.error("Failed to set field "+descriptors[i].getName(),ex);
                }
            }
        }
    }

}

Generated by  Doxygen 1.6.0   Back to index