Package org.apache.jmeter.testbeans.gui

Source Code of org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer$PropertyComparator

/*
* 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.ResourceBundle;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingConstants;

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>
*/
public class GenericTestBeanCustomizer extends JPanel implements SharedCustomizer {
    private static final long serialVersionUID = 240L;

    private static final Logger log = LoggingManager.getLoggerForClass();

    // should be quicker to find the editors if they are registered.
    static {
        PropertyEditorManager.registerEditor(Long.class,    LongPropertyEditor.class);
        PropertyEditorManager.registerEditor(Integer.class, IntegerPropertyEditor.class);
        PropertyEditorManager.registerEditor(Boolean.class, BooleanPropertyEditor.class);
    }

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

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

    /**
     * Array of permissible values.
     * <p>
     * Must be provided if:
     * <ul>
     * <li>{@link #NOT_OTHER} is TRUE, and</li>
     * <li>{@link PropertyEditor#getTags()} is null</li>
     * </ul>
     */
    public static final String TAGS = "tags"; //$NON-NLS-1$

    /**
     * Whether the field must be defined (i.e. is required);
     * Boolean, defaults to FALSE
     */
    public static final String NOT_UNDEFINED = "notUndefined"; //$NON-NLS-1$

    /** Whether the field disallows JMeter expressions; Boolean, default FALSE */
    public static final String NOT_EXPRESSION = "notExpression"; //$NON-NLS-1$

    /** Whether the field disallows constant values different from the provided tags; Boolean, default FALSE */
    public static final String NOT_OTHER = "notOther"; //$NON-NLS-1$

    /** Default value, must be provided if {@link #NOT_UNDEFINED} is TRUE */
    public static final String DEFAULT = "default"; //$NON-NLS-1$

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

    /** Property editor override; must be an enum of type {@link TypeEditor} */
    public static final String GUITYPE = "guiType"; // $NON-NLS-$

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

    public static final String DEFAULT_GROUP = "";

    @SuppressWarnings("unused") // TODO - use or remove
    private int scrollerCount = 0;

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

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

    /**
     * Property editors -- or null if the property can't be edited. Unused if
     * customizerClass==null.
     */
    private transient 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<String, Object> propertyMap;

    /**
     * @deprecated only for use by test code
     */
    @Deprecated
    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
     */
    GenericTestBeanCustomizer(BeanInfo beanInfo) {
        super();

        this.beanInfo = beanInfo;

        // Get and sort the property descriptors:
        descriptors = beanInfo.getPropertyDescriptors();
        Arrays.sort(descriptors, new PropertyComparator(beanInfo));

        // Obtain the propertyEditors:
        editors = new PropertyEditor[descriptors.length];
        for (int i = 0; i < descriptors.length; i++) { // Index is also used for accessing editors array
            PropertyDescriptor descriptor = descriptors[i];
            String name = descriptor.getName();

            // Don't get editors for hidden or non-read-write properties:
            if (descriptor.isHidden() || (descriptor.isExpert() && !JMeterUtils.isExpertMode())
                    || descriptor.getReadMethod() == null || descriptor.getWriteMethod() == null) {
                log.debug("Skipping editor for property " + name);
                editors[i] = null;
                continue;
            }

            PropertyEditor propertyEditor;
            Object guiType = descriptor.getValue(GUITYPE);
            if (guiType instanceof TypeEditor) {
                propertyEditor = ((TypeEditor) guiType).getInstance(descriptor);           
            } else {
                Class<?> editorClass = descriptor.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 = descriptor.getPropertyType();
                    propertyEditor = PropertyEditorManager.findEditor(c);
                }
            }
           
            if (propertyEditor == null) {
                log.warn("No editor for property: " + name
                        + " type: " + descriptor.getPropertyType()
                        + " in bean: " + beanInfo.getBeanDescriptor().getDisplayName()
                        );
                editors[i] = null;
                continue;
            }

            if (log.isDebugEnabled()) {
                log.debug("Property " + name + " has property editor " + propertyEditor);
            }

            validateAttributes(descriptor, propertyEditor);

            if (!propertyEditor.supportsCustomEditor()) {
                propertyEditor = createWrapperEditor(propertyEditor, descriptor);

                if (log.isDebugEnabled()) {
                    log.debug("Editor for property " + name + " is wrapped in " + propertyEditor);
                }
            }
            if(propertyEditor instanceof TestBeanPropertyEditor)
            {
                ((TestBeanPropertyEditor)propertyEditor).setDescriptor(descriptor);
            }
            if (propertyEditor.getCustomEditor() instanceof JScrollPane) {
                scrollerCount++;
            }

            editors[i] = propertyEditor;

            // Initialize the editor with the provided default value or null:
            setEditorValue(i, descriptor.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();
    }

    /**
     * Validate the descriptor attributes.
     *
     * @param pd the descriptor
     * @param pe the propertyEditor
     */
    private static void validateAttributes(PropertyDescriptor pd, PropertyEditor pe) {
        if (notNull(pd) && pd.getValue(DEFAULT) == null) {
            log.warn(getDetails(pd) + " requires a value but does not provide a default.");
        }
        if (notOther(pd) && pd.getValue(TAGS) == null && pe.getTags() == null) {
            log.warn(getDetails(pd) + " does not have tags but other values are not allowed.");
        }
        if (!notNull(pd)) {
            Class<?> propertyType = pd.getPropertyType();
            if (propertyType.isPrimitive()) {
                log.warn(getDetails(pd) + " allows null but is a primitive type");
            }
        }
        if (!pd.attributeNames().hasMoreElements()) {
            log.warn(getDetails(pd) + " does not appear to have been configured");           
        }
    }

    /**
     * Identify the property from the descriptor.
     *
     * @param pd
     * @return
     */
    private static String getDetails(PropertyDescriptor pd) {
        StringBuilder sb = new StringBuilder();
        sb.append(pd.getReadMethod().getDeclaringClass().getName());
        sb.append('#');
        sb.append(pd.getName());
        return sb.toString();
    }

    /**
     * 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 (String editorTag : editorTags) {
                tags[j++] = editorTag;
            }
            for (String additionalTag : additionalTags) {
                tags[j++] = additionalTag;
            }
        }

        boolean notNull = notNull(descriptor);
        boolean notExpression = notExpression(descriptor);
        boolean notOther = notOther(descriptor);

        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;
    }

    /**
     * Returns true if the property disallows constant values different from the provided tags.
     *
     * @param descriptor the property descriptor
     * @return true if the attribute {@link #NOT_OTHER} is defined and equal to Boolean.TRUE;
     *  otherwise the default is false
     */
    static boolean notOther(PropertyDescriptor descriptor) {
        boolean notOther = Boolean.TRUE.equals(descriptor.getValue(NOT_OTHER));
        return notOther;
    }

    /**
     * Returns true if the property does not allow JMeter expressions.
     *
     * @param descriptor the property descriptor
     * @return true if the attribute {@link #NOT_EXPRESSION} is defined and equal to Boolean.TRUE;
     *  otherwise the default is false
     */
    static boolean notExpression(PropertyDescriptor descriptor) {
        boolean notExpression = Boolean.TRUE.equals(descriptor.getValue(NOT_EXPRESSION));
        return notExpression;
    }

    /**
     * Returns true if the property must be defined (i.e. is required);
     *
     * @param descriptor the property descriptor
     * @return true if the attribute {@link #NOT_UNDEFINED} is defined and equal to Boolean.TRUE;
     *  otherwise the default is false
     */
    static boolean notNull(PropertyDescriptor descriptor) {
        boolean notNull = Boolean.TRUE.equals(descriptor.getValue(NOT_UNDEFINED));
        return notNull;
    }

    /**
     * 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 {
        editors[i].setValue(value);
    }


    /**
     * {@inheritDoc}
     * @param map must be an instance of Map&lt;String, Object&gt;
     */
    @SuppressWarnings("unchecked")
    @Override
    public void setObject(Object map) {
        propertyMap = (Map<String, Object>) map;

        if (propertyMap.size() == 0) {
            // Uninitialized -- set it to the defaults:
            for (PropertyDescriptor descriptor : descriptors) {
                Object value = descriptor.getValue(DEFAULT);
                String name = descriptor.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.
     */
    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(SwingConstants.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 static 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) {
        ResourceBundle b = (ResourceBundle) beanInfo.getBeanDescriptor().getValue(RESOURCE_BUNDLE);
        if (b == null) {
            return group;
        }
        String key = new StringBuilder(group).append(".displayName").toString();
        if (b.containsKey(key)) {
            return b.getString(key);
        } else {
            return group;
        }
    }

    /**
     * Comparator used to sort properties for presentation in the GUI.
     */
    private static class PropertyComparator implements Comparator<PropertyDescriptor>, Serializable {
        private static final long serialVersionUID = 240L;

        private final BeanInfo beanInfo;
        public PropertyComparator(BeanInfo beanInfo) {
            this.beanInfo = beanInfo;
        }

        @Override
        public 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 = Integer.valueOf(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 = Integer.valueOf(0);
            }
            return order;
        }
    }

    /**
     * Save values from the GUI fields into the property map
     */
    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.resetValue();
                    }
                } 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);
                }
            }
        }
    }

}
TOP

Related Classes of org.apache.jmeter.testbeans.gui.GenericTestBeanCustomizer$PropertyComparator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.