You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by pm...@apache.org on 2017/12/20 08:50:00 UTC

svn commit: r1818750 - in /jmeter/trunk/src/core/org/apache/jmeter: control/gui/ gui/ gui/util/ testbeans/gui/ threads/ threads/gui/

Author: pmouawad
Date: Wed Dec 20 08:50:00 2017
New Revision: 1818750

URL: http://svn.apache.org/viewvc?rev=1818750&view=rev
Log:
Bug 61919 - UX : Reorder Menus
Contributed by Graham Russell
Reordered menus (1/3)
This comment #360
Bugzilla Id: 61919

Added:
    jmeter/trunk/src/core/org/apache/jmeter/gui/GUIMenuSortOrder.java   (with props)
    jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuSeparatorInfo.java   (with props)
Modified:
    jmeter/trunk/src/core/org/apache/jmeter/control/gui/TestPlanGui.java
    jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuFactory.java
    jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuInfo.java
    jmeter/trunk/src/core/org/apache/jmeter/testbeans/gui/TestBeanGUI.java
    jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java
    jmeter/trunk/src/core/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java

Modified: jmeter/trunk/src/core/org/apache/jmeter/control/gui/TestPlanGui.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/control/gui/TestPlanGui.java?rev=1818750&r1=1818749&r2=1818750&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/control/gui/TestPlanGui.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/control/gui/TestPlanGui.java Wed Dec 20 08:50:00 2017
@@ -41,7 +41,6 @@ import org.apache.jmeter.util.JMeterUtil
 /**
  * JMeter GUI component representing the test plan which will be executed when
  * the test is run.
- *
  */
 public class TestPlanGui extends AbstractJMeterGuiComponent {
 
@@ -91,14 +90,18 @@ public class TestPlanGui extends Abstrac
         JPopupMenu pop = new JPopupMenu();
         JMenu addMenu = new JMenu(JMeterUtils.getResString("add")); // $NON-NLS-1$
         addMenu.add(MenuFactory.makeMenu(MenuFactory.THREADS, ActionNames.ADD));
-        addMenu.add(MenuFactory.makeMenu(MenuFactory.FRAGMENTS, ActionNames.ADD));
-        addMenu.add(MenuFactory.makeMenu(MenuFactory.NON_TEST_ELEMENTS, ActionNames.ADD));
+        addMenu.addSeparator();
         addMenu.add(MenuFactory.makeMenu(MenuFactory.CONFIG_ELEMENTS, ActionNames.ADD));
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.LISTENERS, ActionNames.ADD));
+        addMenu.addSeparator();
         addMenu.add(MenuFactory.makeMenu(MenuFactory.TIMERS, ActionNames.ADD));
+        addMenu.addSeparator();
         addMenu.add(MenuFactory.makeMenu(MenuFactory.PRE_PROCESSORS, ActionNames.ADD));
         addMenu.add(MenuFactory.makeMenu(MenuFactory.POST_PROCESSORS, ActionNames.ADD));
         addMenu.add(MenuFactory.makeMenu(MenuFactory.ASSERTIONS, ActionNames.ADD));
-        addMenu.add(MenuFactory.makeMenu(MenuFactory.LISTENERS, ActionNames.ADD));
+        addMenu.addSeparator();
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.FRAGMENTS, ActionNames.ADD));
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.NON_TEST_ELEMENTS, ActionNames.ADD));
         pop.add(addMenu);
         MenuFactory.addPasteResetMenu(pop);
         MenuFactory.addFileMenu(pop, false);

Added: jmeter/trunk/src/core/org/apache/jmeter/gui/GUIMenuSortOrder.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/gui/GUIMenuSortOrder.java?rev=1818750&view=auto
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/gui/GUIMenuSortOrder.java (added)
+++ jmeter/trunk/src/core/org/apache/jmeter/gui/GUIMenuSortOrder.java Wed Dec 20 08:50:00 2017
@@ -0,0 +1,33 @@
+/*
+ * 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.gui;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to allow specific ordering of this item in the GUI Menu
+ * @since 4.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface GUIMenuSortOrder {
+    int value() default Integer.MAX_VALUE;
+}

Propchange: jmeter/trunk/src/core/org/apache/jmeter/gui/GUIMenuSortOrder.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/core/org/apache/jmeter/gui/GUIMenuSortOrder.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuFactory.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuFactory.java?rev=1818750&r1=1818749&r2=1818750&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuFactory.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuFactory.java Wed Dec 20 08:50:00 2017
@@ -21,17 +21,15 @@ package org.apache.jmeter.gui.util;
 import java.awt.Component;
 import java.awt.HeadlessException;
 import java.io.IOException;
-import java.io.Serializable;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
-import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -40,6 +38,7 @@ import javax.swing.JMenuItem;
 import javax.swing.JPopupMenu;
 import javax.swing.KeyStroke;
 import javax.swing.MenuElement;
+import javax.swing.tree.DefaultMutableTreeNode;
 
 import org.apache.jmeter.control.Controller;
 import org.apache.jmeter.control.TestFragmentController;
@@ -61,7 +60,6 @@ import org.apache.jmeter.util.JMeterUtil
 import org.apache.jmeter.visualizers.Printable;
 import org.apache.jorphan.gui.GuiUtils;
 import org.apache.jorphan.reflect.ClassFinder;
-import org.apache.jorphan.util.JOrphanUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -70,112 +68,144 @@ public final class MenuFactory {
 
     /*
      *  Predefined strings for makeMenu().
-     *  These are used as menu categories in the menuMap Hashmap,
+     *  These are used as menu categories in the menuMap HashMap,
      *  and also for resource lookup in messages.properties
+     *  TODO: why isn't this an enum?
     */
     public static final String THREADS = "menu_threads"; //$NON-NLS-1$
-    
     public static final String FRAGMENTS = "menu_fragments"; //$NON-NLS-1$
-
     public static final String TIMERS = "menu_timer"; //$NON-NLS-1$
-
     public static final String CONTROLLERS = "menu_logic_controller"; //$NON-NLS-1$
-
     public static final String SAMPLERS = "menu_generative_controller"; //$NON-NLS-1$
-
     public static final String CONFIG_ELEMENTS = "menu_config_element"; //$NON-NLS-1$
-
     public static final String POST_PROCESSORS = "menu_post_processors"; //$NON-NLS-1$
-
     public static final String PRE_PROCESSORS = "menu_pre_processors"; //$NON-NLS-1$
-
     public static final String ASSERTIONS = "menu_assertions"; //$NON-NLS-1$
-
     public static final String NON_TEST_ELEMENTS = "menu_non_test_elements"; //$NON-NLS-1$
-
     public static final String LISTENERS = "menu_listener"; //$NON-NLS-1$
+    public static final String SEPARATOR = "menu_separator"; //$NON-NLS-1$
 
-    private static final Map<String, List<MenuInfo>> menuMap = new HashMap<>();
+    private static final Map<String, List<MenuInfo>> menuMap;
 
-    private static final Set<String> elementsToSkip = new HashSet<>();
+    static {
+        menuMap = new HashMap<>();
+        menuMap.put(THREADS, new LinkedList<>());
+        menuMap.put(TIMERS, new LinkedList<>());
+        menuMap.put(ASSERTIONS, new LinkedList<>());
+        menuMap.put(CONFIG_ELEMENTS, new LinkedList<>());
+        menuMap.put(CONTROLLERS, new LinkedList<>());
+        menuMap.put(LISTENERS, new LinkedList<>());
+        menuMap.put(NON_TEST_ELEMENTS, new LinkedList<>());
+        menuMap.put(SAMPLERS, new LinkedList<>());
+        menuMap.put(POST_PROCESSORS, new LinkedList<>());
+        menuMap.put(PRE_PROCESSORS, new LinkedList<>());
+        menuMap.put(FRAGMENTS, new LinkedList<>());
+        menuMap.put(SEPARATOR, Collections.singletonList(new MenuSeparatorInfo()));
 
-    // MENU_ADD_xxx - controls which items are in the ADD menu
-    // MENU_PARENT_xxx - controls which items are in the Insert Parent menu
-    private static final String[] MENU_ADD_CONTROLLER = new String[] {
-        MenuFactory.CONTROLLERS,
-        MenuFactory.CONFIG_ELEMENTS,
-        MenuFactory.TIMERS,
-        MenuFactory.PRE_PROCESSORS,
-        MenuFactory.SAMPLERS,
-        MenuFactory.POST_PROCESSORS,
-        MenuFactory.ASSERTIONS,
-        MenuFactory.LISTENERS,
-        };
-
-    private static final String[] MENU_PARENT_CONTROLLER = new String[] {
-        MenuFactory.CONTROLLERS };
-
-    private static final String[] MENU_ADD_SAMPLER = new String[] {
-        MenuFactory.CONFIG_ELEMENTS,
-        MenuFactory.TIMERS,
-        MenuFactory.PRE_PROCESSORS,
-        MenuFactory.POST_PROCESSORS,
-        MenuFactory.ASSERTIONS,
-        MenuFactory.LISTENERS,
-        };
-
-    private static final String[] MENU_PARENT_SAMPLER = new String[] {
-        MenuFactory.CONTROLLERS };
-
-    private static final List<MenuInfo> timers;
-    private static final List<MenuInfo> controllers;
-    private static final List<MenuInfo> samplers;
-    private static final List<MenuInfo> threads;
-    private static final List<MenuInfo> fragments;
-    private static final List<MenuInfo> configElements;
-    private static final List<MenuInfo> assertions;
-    private static final List<MenuInfo> listeners;
-    private static final List<MenuInfo> nonTestElements;
-    private static final List<MenuInfo> postProcessors;
-    private static final List<MenuInfo> preProcessors;
+        try {
+            initializeMenus(menuMap, classesToSkip());
+            sortMenus(menuMap.values());
+            separateItemsWithExplicitOrder(menuMap.values());
+        } catch (Error | RuntimeException ex) { // NOSONAR We want to log Errors in jmeter.log
+            log.error("Error initializing menus, check configuration if using 3rd party libraries", ex);
+            throw ex;
+        } catch (Exception ex) {
+            log.error("Error initializing menus, check configuration if using 3rd party libraries", ex);
+        }
+    }
 
-    static {
-        threads = new LinkedList<>();
-        fragments = new LinkedList<>();
-        timers = new LinkedList<>();
-        controllers = new LinkedList<>();
-        samplers = new LinkedList<>();
-        configElements = new LinkedList<>();
-        assertions = new LinkedList<>();
-        listeners = new LinkedList<>();
-        postProcessors = new LinkedList<>();
-        preProcessors = new LinkedList<>();
-        nonTestElements = new LinkedList<>();
-        menuMap.put(THREADS, threads);
-        menuMap.put(FRAGMENTS, fragments);
-        menuMap.put(TIMERS, timers);
-        menuMap.put(ASSERTIONS, assertions);
-        menuMap.put(CONFIG_ELEMENTS, configElements);
-        menuMap.put(CONTROLLERS, controllers);
-        menuMap.put(LISTENERS, listeners);
-        menuMap.put(NON_TEST_ELEMENTS, nonTestElements);
-        menuMap.put(SAMPLERS, samplers);
-        menuMap.put(POST_PROCESSORS, postProcessors);
-        menuMap.put(PRE_PROCESSORS, preProcessors);
+    private static Set<String> classesToSkip() {
+        return Arrays.stream(JMeterUtils.getPropDefault("not_in_menu", "").split(","))
+                .map(String::trim)
+                .collect(Collectors.toSet());
+    }
+
+    private static void initializeMenus(
+            Map<String, List<MenuInfo>> menus, Set<String> elementsToSkip) {
         try {
-            String[] classesToSkip =
-                JOrphanUtils.split(JMeterUtils.getPropDefault("not_in_menu", ""), ","); //$NON-NLS-1$
-            for (String aClassesToSkip : classesToSkip) {
-                elementsToSkip.add(aClassesToSkip.trim());
+            List<String> guiClasses = ClassFinder
+                    .findClassesThatExtend(
+                            JMeterUtils.getSearchPaths(),
+                            new Class[] {JMeterGUIComponent.class, TestBean.class})
+                    .stream()
+                    // JMeterTreeNode and TestBeanGUI are special GUI classes,
+                    // and aren't intended to be added to menus
+                    .filter(name -> !name.endsWith("JMeterTreeNode"))
+                    .filter(name -> !name.endsWith("TestBeanGUI"))
+                    .filter(name -> !elementsToSkip.contains(name))
+                    .distinct()
+                    .map(String::trim)
+                    .collect(Collectors.toList());
+
+            for (String className : guiClasses) {
+                JMeterGUIComponent item = getGUIComponent(className, elementsToSkip);
+                if (item == null) {
+                    continue;
+                }
+
+                Collection<String> categories = item.getMenuCategories();
+                if (categories == null) {
+                    log.debug("{} participates in no menus.", className);
+                    continue;
+                }
+                for (String key: menus.keySet()) {
+                    if (categories.contains(key)) {
+                        menus.get(key).add(new MenuInfo(item, className));
+                    }
+                }
             }
+        } catch (IOException e) {
+            log.error("IO Exception while initializing menus.", e);
+        }
+    }
 
-            initializeMenus();
-            sortPluginMenus();
-        } catch (Error | RuntimeException ex) { // NOSONAR We want to log Errors in jmeter.log 
-            log.error("Error initializing menus in static bloc, check configuration if using 3rd party libraries", ex);
-            throw ex;
-        } catch (Exception ex) {
-            log.error("Error initializing menus in static bloc, check configuration if using 3rd party libraries", ex);
+    private static JMeterGUIComponent getGUIComponent(
+            String name, Set<String> elementsToSkip) {
+        JMeterGUIComponent item = null;
+        boolean hideBean = false; // Should the TestBean be hidden?
+        try {
+            Class<?> c = Class.forName(name);
+            if (TestBean.class.isAssignableFrom(c)) {
+                TestBeanGUI testBeanGUI = new TestBeanGUI(c);
+                hideBean = testBeanGUI.isHidden()
+                        || (testBeanGUI.isExpert() && !JMeterUtils.isExpertMode());
+                item = testBeanGUI;
+            } else {
+                item = (JMeterGUIComponent) c.newInstance();
+            }
+        } catch (NoClassDefFoundError e) {
+            log.warn("Configuration error, probably corrupt or missing third party library(jar)? Could not create class: {}.",
+                    name, e);
+        } catch (HeadlessException e) {
+            log.warn("Could not instantiate class: {}", name, e);
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            log.warn("Could not instantiate class: {}", name, e);
+        }
+        if (hideBean || (item != null && elementsToSkip.contains(item.getStaticLabel()))) {
+            log.info("Skipping {}", name);
+            item = null;
+        }
+        return item;
+    }
+
+    private static void sortMenus(Collection<List<MenuInfo>> menus) {
+        for (List<MenuInfo> menu : menus) {
+            menu.sort(Comparator.comparing(MenuInfo::getLabel));
+            menu.sort(Comparator.comparingInt(MenuInfo::getSortOrder));
+        }
+    }
+
+    private static void separateItemsWithExplicitOrder(Collection<List<MenuInfo>> menus) {
+        for (List<MenuInfo> menu : menus) {
+            Optional<MenuInfo> firstDefaultSortItem = menu.stream()
+                    .filter(info -> info.getSortOrder() == MenuInfo.SORT_ORDER_DEFAULT)
+                    .findFirst();
+            int index = menu.indexOf(firstDefaultSortItem.orElseThrow(IllegalStateException::new));
+            if (index > 0) {
+                menu.add(index, new MenuSeparatorInfo());
+            }
         }
     }
 
@@ -222,7 +252,8 @@ public final class MenuFactory {
         menu.add(makeMenuItemRes("menu_merge", ActionNames.MERGE));// $NON-NLS-1$
         menu.add(makeMenuItemRes("save_as", ActionNames.SAVE_AS));// $NON-NLS-1$
         if(addSaveTestFragmentMenu) {
-            menu.add(makeMenuItemRes("save_as_test_fragment", ActionNames.SAVE_AS_TEST_FRAGMENT));// $NON-NLS-1$
+            menu.add(makeMenuItemRes("save_as_test_fragment", // $NON-NLS-1$
+                    ActionNames.SAVE_AS_TEST_FRAGMENT));
         }
         addSeparator(menu);
         JMenuItem savePicture = makeMenuItemRes("save_as_image",// $NON-NLS-1$
@@ -254,7 +285,8 @@ public final class MenuFactory {
     }
 
     /**
-     * Add undo / redo
+     * Add undo / redo to the provided menu
+     *
      * @param menu JPopupMenu
      */
     private static void addUndoItems(JPopupMenu menu) {
@@ -265,7 +297,8 @@ public final class MenuFactory {
         menu.add(undo);
 
         JMenuItem redo = makeMenuItemRes("redo", ActionNames.REDO); //$NON-NLS-1$
-        // TODO: we could even show some hints on action being undone here if this will be required (by passing those hints into history  records)
+        // TODO: we could even show some hints on action being undone here
+        // if required (by passing those hints into history records)
         redo.setEnabled(GuiPackage.getInstance().canRedo());
         menu.add(redo);
     }
@@ -273,29 +306,27 @@ public final class MenuFactory {
 
     public static JMenu makeMenus(String[] categories, String label, String actionCommand) {
         JMenu addMenu = new JMenu(label);
-        for (String category : categories) {
-            addMenu.add(makeMenu(category, actionCommand));
-        }
+        Arrays.stream(categories)
+                .map(category -> makeMenu(category, actionCommand))
+                .forEach(addMenu::add);
         GuiUtils.makeScrollableMenu(addMenu);
         return addMenu;
     }
 
     public static JPopupMenu getDefaultControllerMenu() {
         JPopupMenu pop = new JPopupMenu();
-        pop.add(MenuFactory.makeMenus(MENU_ADD_CONTROLLER,
-                JMeterUtils.getResString("add"),// $NON-NLS-1$
-                ActionNames.ADD));
+        pop.add(createDefaultAddMenu());
         pop.add(MenuFactory.makeMenuItemRes("add_think_times",// $NON-NLS-1$
                 ActionNames.ADD_THINK_TIME_BETWEEN_EACH_STEP));
 
         pop.add(MenuFactory.makeMenuItemRes("apply_naming",// $NON-NLS-1$
                 ActionNames.APPLY_NAMING_CONVENTION));
         
-        pop.add(makeMenus(MENU_PARENT_CONTROLLER,
+        pop.add(makeMenus(new String[]{CONTROLLERS},
                 JMeterUtils.getResString("change_parent"),// $NON-NLS-1$
                 ActionNames.CHANGE_PARENT));
 
-        pop.add(makeMenus(MENU_PARENT_CONTROLLER,
+        pop.add(makeMenus(new String[]{CONTROLLERS},
                 JMeterUtils.getResString("insert_parent"),// $NON-NLS-1$
                 ActionNames.ADD_PARENT));
         MenuFactory.addEditMenu(pop, true);
@@ -303,12 +334,25 @@ public final class MenuFactory {
         return pop;
     }
 
+    private static JMenu createDefaultAddMenu() {
+        String addAction = ActionNames.ADD;
+        JMenu addMenu = new JMenu(JMeterUtils.getResString("add")); // $NON-NLS-1$
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.ASSERTIONS, addAction));
+        addMenu.addSeparator();
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.TIMERS, addAction));
+        addMenu.addSeparator();
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.PRE_PROCESSORS, addAction));
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.POST_PROCESSORS, addAction));
+        addMenu.addSeparator();
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.CONFIG_ELEMENTS, addAction));
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.LISTENERS, addAction));
+        return addMenu;
+    }
+
     public static JPopupMenu getDefaultSamplerMenu() {
         JPopupMenu pop = new JPopupMenu();
-        pop.add(MenuFactory.makeMenus(MENU_ADD_SAMPLER,
-                JMeterUtils.getResString("add"),// $NON-NLS-1$
-                ActionNames.ADD));
-        pop.add(makeMenus(MENU_PARENT_SAMPLER,
+        pop.add(createDefaultAddMenu());
+        pop.add(makeMenus(new String[]{CONTROLLERS},
                 JMeterUtils.getResString("insert_parent"),// $NON-NLS-1$
                 ActionNames.ADD_PARENT));
         MenuFactory.addEditMenu(pop, true);
@@ -325,8 +369,8 @@ public final class MenuFactory {
 
     public static JPopupMenu getDefaultVisualizerMenu() {
         JPopupMenu pop = new JPopupMenu();
-        pop.add(
-                MenuFactory.makeMenuItemRes("clear", ActionNames.CLEAR)); //$NON-NLS-1$
+        pop.add(MenuFactory.makeMenuItemRes(
+                "clear", ActionNames.CLEAR)); //$NON-NLS-1$
         MenuFactory.addEditMenu(pop, true);
         MenuFactory.addFileMenu(pop);
         return pop;
@@ -363,29 +407,35 @@ public final class MenuFactory {
     /**
      * Create a menu from a menu category.
      *
-     * @param category - predefined string (used as key for menuMap HashMap and messages.properties lookup)
-     * @param actionCommand - predefined string, e.g. ActionNames.ADD
-     *     @see org.apache.jmeter.gui.action.ActionNames
+     * @param category      predefined string (used as key for menuMap HashMap
+     *                      and messages.properties lookup)
+     * @param actionCommand predefined string, e.g. {@code }ActionNames.ADD}
+     *                      {@link ActionNames}
      * @return the menu
      */
     public static JMenu makeMenu(String category, String actionCommand) {
-        return makeMenu(menuMap.get(category), actionCommand, JMeterUtils.getResString(category));
+        return makeMenu(
+                menuMap.get(category),
+                actionCommand,
+                JMeterUtils.getResString(category));
     }
 
     /**
      * Create a menu from a collection of items.
      *
-     * @param menuInfo - collection of MenuInfo items
-     * @param actionCommand - predefined string, e.g. ActionNames.ADD
-     *     @see org.apache.jmeter.gui.action.ActionNames
+     * @param menuInfo      collection of MenuInfo items
+     * @param actionCommand predefined string, e.g. ActionNames.ADD
+     *                      {@link ActionNames}
      * @param menuName The name of the newly created menu
      * @return the menu
      */
-    public static JMenu makeMenu(Collection<MenuInfo> menuInfo, String actionCommand, String menuName) {
+    private static JMenu makeMenu(
+            Collection<MenuInfo> menuInfo, String actionCommand, String menuName) {
+
         JMenu menu = new JMenu(menuName);
-        for (MenuInfo info : menuInfo) {
-            menu.add(makeMenuItem(info, actionCommand));
-        }
+        menuInfo.stream()
+                .map(info -> makeMenuItem(info, actionCommand))
+                .forEach(menu::add);
         GuiUtils.makeScrollableMenu(menu);
         return menu;
     }
@@ -401,8 +451,8 @@ public final class MenuFactory {
      *
      * @param label for the MenuItem
      * @param name for the MenuItem
-     * @param actionCommand - predefined string, e.g. ActionNames.ADD
-     *     @see org.apache.jmeter.gui.action.ActionNames
+     * @param actionCommand predefined string, e.g. ActionNames.ADD
+     *                      {@link ActionNames}
      * @return the menu item
      */
     public static JMenuItem makeMenuItem(String label, String name, String actionCommand) {
@@ -420,8 +470,8 @@ public final class MenuFactory {
      * Create a single menu item from the resource name.
      *
      * @param resource for the MenuItem
-     * @param actionCommand - predefined string, e.g. ActionNames.ADD
-     *     @see org.apache.jmeter.gui.action.ActionNames
+     * @param actionCommand predefined string, e.g. ActionNames.ADD
+     *                      {@link ActionNames}
      * @return the menu item
      */
     public static JMenuItem makeMenuItemRes(String resource, String actionCommand) {
@@ -439,11 +489,15 @@ public final class MenuFactory {
      * Create a single menu item from a MenuInfo object
      *
      * @param info the MenuInfo object
-     * @param actionCommand - predefined string, e.g. ActionNames.ADD
-     *     @see org.apache.jmeter.gui.action.ActionNames
+     * @param actionCommand predefined string, e.g. ActionNames.ADD
+     *                      {@link ActionNames}
      * @return the menu item
      */
-    public static Component makeMenuItem(MenuInfo info, String actionCommand) {
+    private static Component makeMenuItem(MenuInfo info, String actionCommand) {
+        if (info instanceof MenuSeparatorInfo) {
+            return new JPopupMenu.Separator();
+        }
+
         JMenuItem newMenuChoice = new JMenuItem(info.getLabel());
         newMenuChoice.setName(info.getClassName());
         newMenuChoice.addActionListener(ActionRouter.getInstance());
@@ -454,115 +508,18 @@ public final class MenuFactory {
         return newMenuChoice;
     }
 
-    public static JMenuItem makeMenuItemRes(String resource, String actionCommand, KeyStroke accel) {
+    private static JMenuItem makeMenuItemRes(String resource, String actionCommand, KeyStroke accel) {
         JMenuItem item = makeMenuItemRes(resource, actionCommand);
         item.setAccelerator(accel);
         return item;
     }
 
-    public static JMenuItem makeMenuItem(String label, String name, String actionCommand, KeyStroke accel) {
+    private static JMenuItem makeMenuItem(String label, String name, String actionCommand, KeyStroke accel) {
         JMenuItem item = makeMenuItem(label, name, actionCommand);
         item.setAccelerator(accel);
         return item;
     }
 
-    private static void initializeMenus() {
-        try {
-            List<String> guiClasses = ClassFinder
-                    .findClassesThatExtend(
-                        JMeterUtils.getSearchPaths(),
-                        new Class[] {JMeterGUIComponent.class, TestBean.class})
-                    .stream()
-                    // JMeterTreeNode and TestBeanGUI are special GUI classes,
-                    // and aren't intended to be added to menus
-                    .filter(name -> !name.endsWith("JMeterTreeNode"))
-                    .filter(name -> !name.endsWith("TestBeanGUI"))
-                    .filter(name -> !elementsToSkip.contains(name))
-                    .distinct()
-                    .sorted(Comparator.comparing(String::trim))
-                    .collect(Collectors.toList());
-
-            for (String name : guiClasses) {
-                JMeterGUIComponent item = getItem(name);
-                if (item == null) {
-                    continue;
-                }
-
-                Collection<String> categories = item.getMenuCategories();
-                if (categories == null) {
-                    log.debug("{} participates in no menus.", name);
-                    continue;
-                }
-                if (categories.contains(THREADS)) {
-                    threads.add(new MenuInfo(item, name));
-                }
-                if (categories.contains(FRAGMENTS)) {
-                    fragments.add(new MenuInfo(item, name));
-                }
-                if (categories.contains(TIMERS)) {
-                    timers.add(new MenuInfo(item, name));
-                }
-                if (categories.contains(POST_PROCESSORS)) {
-                    postProcessors.add(new MenuInfo(item, name));
-                }
-                if (categories.contains(PRE_PROCESSORS)) {
-                    preProcessors.add(new MenuInfo(item, name));
-                }
-                if (categories.contains(CONTROLLERS)) {
-                    controllers.add(new MenuInfo(item, name));
-                }
-                if (categories.contains(SAMPLERS)) {
-                    samplers.add(new MenuInfo(item, name));
-                }
-                if (categories.contains(NON_TEST_ELEMENTS)) {
-                    nonTestElements.add(new MenuInfo(item, name));
-                }
-                if (categories.contains(LISTENERS)) {
-                    listeners.add(new MenuInfo(item, name));
-                }
-                if (categories.contains(CONFIG_ELEMENTS)) {
-                    configElements.add(new MenuInfo(item, name));
-                }
-                if (categories.contains(ASSERTIONS)) {
-                    assertions.add(new MenuInfo(item, name));
-                }
-            }
-        } catch (IOException e) {
-            log.error("IO Exception while initializing menus.", e);
-        }
-    }
-
-    private static JMeterGUIComponent getItem(String name) {
-        JMeterGUIComponent item = null;
-        boolean hideBean = false; // Should the TestBean be hidden?
-        try {
-            Class<?> c = Class.forName(name);
-            if (TestBean.class.isAssignableFrom(c)) {
-                TestBeanGUI tbgui = new TestBeanGUI(c);
-                hideBean = tbgui.isHidden() || (tbgui.isExpert() && !JMeterUtils.isExpertMode());
-                item = tbgui;
-            } else {
-                item = (JMeterGUIComponent) c.newInstance();
-            }
-        } catch (NoClassDefFoundError e) {
-            log.warn("Configuration error, probably corrupt or missing third party library(jar) ? Could not create class: {}.",
-                    name, e);
-        } catch (HeadlessException e) {
-            log.warn("Could not instantiate class: {}", name, e);
-        } catch (RuntimeException e) {
-            throw e;
-        } catch (Exception e) {
-            log.warn("Could not instantiate class: {}", name, e);
-        }
-        if (hideBean || (item != null && elementsToSkip.contains(item.getStaticLabel()))) {
-            log.info("Skipping {}", name);
-            item = null;
-        } else {
-            elementsToSkip.add(name);
-        }
-        return item;
-    }
-
     private static void addSeparator(JPopupMenu menu) {
         MenuElement[] elements = menu.getSubElements();
         if ((elements.length > 0)
@@ -573,14 +530,12 @@ public final class MenuFactory {
 
     /**
      * Determine whether or not nodes can be added to this parent.
-     *
+     * <p>
      * Used by Merge
      *
-     * @param parentNode
-     *            The {@link JMeterTreeNode} to test, if a new element can be
-     *            added to it
-     * @param element
-     *            - top-level test element to be added
+     * @param parentNode The {@link JMeterTreeNode} to test, if a new element
+     *                   can be added to it
+     * @param element    top-level test element to be added
      * @return whether it is OK to add the element to this parent
      */
     public static boolean canAddTo(JMeterTreeNode parentNode, TestElement element) {
@@ -590,14 +545,12 @@ public final class MenuFactory {
 
     /**
      * Determine whether or not nodes can be added to this parent.
-     *
+     * <p>
      * Used by DragNDrop and Paste.
      *
-     * @param parentNode
-     *            The {@link JMeterTreeNode} to test, if <code>nodes[]</code>
+     * @param parentNode The {@link JMeterTreeNode} to test, if <code>nodes[]</code>
      *            can be added to it
-     * @param nodes
-     *            - array of nodes that are to be added
+     * @param nodes      array of nodes that are to be added
      * @return whether it is OK to add the dragged nodes to this parent
      */
     public static boolean canAddTo(JMeterTreeNode parentNode, JMeterTreeNode[] nodes) {
@@ -619,19 +572,22 @@ public final class MenuFactory {
         }
 
         if (parent instanceof TestPlan) {
-            return !foundClass(
-                    nodes,
-                    new Class[]{Sampler.class, Controller.class}, // Samplers and Controllers need not apply ...
-                    new Class[]{AbstractThreadGroup.class, NonTestElement.class});
+            List<Class> samplerAndController = Arrays.asList(Sampler.class, Controller.class);
+            List<Class> exceptions = Arrays.asList(AbstractThreadGroup.class, NonTestElement.class);
+            return !foundClass(nodes, samplerAndController, exceptions);
         }
         // AbstractThreadGroup is only allowed under a TestPlan
         if (foundClass(nodes, new Class[]{AbstractThreadGroup.class})) {
             return false;
         }
-        if (parent instanceof Controller) {// Includes thread group; anything goes
+
+        // Includes thread group; anything goes
+        if (parent instanceof Controller) {
             return true;
         }
-        if (parent instanceof Sampler) {// Samplers and Controllers need not apply ...
+
+        // No Samplers and Controllers
+        if (parent instanceof Sampler) {
             return !foundClass(nodes, new Class[]{Sampler.class, Controller.class});
         }
 
@@ -641,11 +597,12 @@ public final class MenuFactory {
 
     /**
      * Is any of nodes an instance of one of the classes?
+     *
      * @param nodes Array of {@link JMeterTreeNode}
      * @param classes Array of {@link Class}
      * @return true if nodes is one of classes
      */
-    private static boolean foundClass(JMeterTreeNode[] nodes, Class<?>[] classes) {
+    private static boolean foundClass(JMeterTreeNode[] nodes, Class[] classes) {
         for (JMeterTreeNode node : nodes) {
             for (Class<?> aClass : classes) {
                 if (aClass.isInstance(node.getUserObject())) {
@@ -670,94 +627,17 @@ public final class MenuFactory {
 
     /**
      * Is any node an instance of one of the classes, but not an exceptions?
+     *
      * @param nodes array of {@link JMeterTreeNode}
      * @param classes Array of {@link Class}
      * @param exceptions Array of {@link Class}
      * @return boolean
      */
-    private static boolean foundClass(JMeterTreeNode[] nodes, Class<?>[] classes, Class<?>[] exceptions) {
-        for (JMeterTreeNode node : nodes) {
-            Object userObject = node.getUserObject();
-            boolean isException = false;
-            for (Class<?> except : exceptions) {
-                if (except.isInstance(userObject)) {
-                    isException = true;
-                    break;
-                }
-            }
-            if (!isException) {
-                for (Class<?> aClass : classes) {
-                    if (aClass.isInstance(userObject)) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    // Methods used for Test cases
-    static int menuMap_size() {
-        return menuMap.size();
-    }
-    static int assertions_size() {
-        return assertions.size();
-    }
-    static int configElements_size() {
-        return configElements.size();
-    }
-    static int controllers_size() {
-        return controllers.size();
-    }
-    static int listeners_size() {
-        return listeners.size();
-    }
-    static int nonTestElements_size() {
-        return nonTestElements.size();
-    }
-    static int postProcessors_size() {
-        return postProcessors.size();
-    }
-    static int preProcessors_size() {
-        return preProcessors.size();
-    }
-    static int samplers_size() {
-        return samplers.size();
-    }
-    static int timers_size() {
-        return timers.size();
-    }
-    static int elementsToSkip_size() {
-        return elementsToSkip.size();
-    }
-
-    /**
-     * Menu sort helper class
-     */
-    private static class MenuInfoComparator implements Comparator<MenuInfo>, Serializable {
-        private static final long serialVersionUID = 1L;
-        private final boolean caseBlind;
-        MenuInfoComparator(boolean caseBlind){
-            this.caseBlind = caseBlind;
-        }
-        @Override
-        public int compare(MenuInfo o1, MenuInfo o2) {
-            String lab1 = o1.getLabel();
-            String lab2 = o2.getLabel();
-            if (caseBlind) {
-                return lab1.toLowerCase(Locale.ENGLISH).compareTo(lab2.toLowerCase(Locale.ENGLISH));
-            }
-            return lab1.compareTo(lab2);
-        }
-    }
-
-    /**
-     * Sort loaded menus; all but THREADS are sorted case-blind.
-     * [This is so Thread Group appears before setUp and tearDown]
-     */
-    private static void sortPluginMenus() {
-        for (Entry<String, List<MenuInfo>> me : menuMap.entrySet()) {
-            me.getValue().sort(new MenuInfoComparator(!me.getKey().equals(THREADS)));
-        }
+    private static boolean foundClass(
+            JMeterTreeNode[] nodes, List<Class> classes, List<Class> exceptions) {
+        return Arrays.stream(nodes)
+                .map(DefaultMutableTreeNode::getUserObject)
+                .filter(userObj -> exceptions.stream().noneMatch(c -> c.isInstance(userObj)))
+                .anyMatch(userObj -> classes.stream().anyMatch(c -> c.isInstance(userObj)));
     }
 }

Modified: jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuInfo.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuInfo.java?rev=1818750&r1=1818749&r2=1818750&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuInfo.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuInfo.java Wed Dec 20 08:50:00 2017
@@ -18,6 +18,7 @@
 
 package org.apache.jmeter.gui.util;
 
+import org.apache.jmeter.gui.GUIMenuSortOrder;
 import org.apache.jmeter.gui.JMeterGUIComponent;
 
 /**
@@ -25,25 +26,39 @@ import org.apache.jmeter.gui.JMeterGUICo
  */
 public class MenuInfo {
 
+    public static final int SORT_ORDER_DEFAULT = Integer.MAX_VALUE;
     private final String label;
-
     private final String className;
-
     private final JMeterGUIComponent guiComp;
+    private final int sortOrder;
 
     public MenuInfo(String displayLabel, String classFullName) {
         label = displayLabel;
         className = classFullName;
         guiComp = null;
+        sortOrder = getSortOrderFromName(classFullName);
     }
 
     public MenuInfo(JMeterGUIComponent item, String classFullName) {
         label = item.getStaticLabel();
         className = classFullName;
         guiComp = item;
+        sortOrder = getSortOrderFromName(classFullName);
     }
 
-    public String getLabel(){
+    private int getSortOrderFromName(String classFullName) {
+        try {
+            GUIMenuSortOrder menuSortOrder = Class.forName(classFullName)
+                    .getDeclaredAnnotation(GUIMenuSortOrder.class);
+            if (menuSortOrder != null) {
+                return menuSortOrder.value();
+            }
+        } catch (ClassNotFoundException ignored) {
+        }
+        return SORT_ORDER_DEFAULT;
+    }
+
+    public String getLabel() {
         if (guiComp != null) {
             return guiComp.getStaticLabel();
         }
@@ -53,4 +68,8 @@ public class MenuInfo {
     public String getClassName(){
         return className;
     }
+
+    public int getSortOrder() {
+        return sortOrder;
+    }
 }

Added: jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuSeparatorInfo.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuSeparatorInfo.java?rev=1818750&view=auto
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuSeparatorInfo.java (added)
+++ jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuSeparatorInfo.java Wed Dec 20 08:50:00 2017
@@ -0,0 +1,28 @@
+/*
+ * 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.gui.util;
+
+/**
+ * Indicates a menu separator when building {@link MenuInfo} lists in {@link MenuFactory}
+ * @since 4.0
+ */
+public class MenuSeparatorInfo extends MenuInfo {
+    public MenuSeparatorInfo() {
+        super("separator", "javax.swing.JPopupMenu.Separator");
+    }
+}

Propchange: jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuSeparatorInfo.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/core/org/apache/jmeter/gui/util/MenuSeparatorInfo.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jmeter/trunk/src/core/org/apache/jmeter/testbeans/gui/TestBeanGUI.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/testbeans/gui/TestBeanGUI.java?rev=1818750&r1=1818749&r2=1818750&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/testbeans/gui/TestBeanGUI.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/testbeans/gui/TestBeanGUI.java Wed Dec 20 08:50:00 2017
@@ -94,9 +94,7 @@ public class TestBeanGUI extends Abstrac
     private static final Logger log = LoggerFactory.getLogger(TestBeanGUI.class);
 
     private final Class<?> testBeanClass;
-
     private transient BeanInfo beanInfo;
-
     private final Class<?> customizerClass;
 
     /**
@@ -114,19 +112,13 @@ public class TestBeanGUI extends Abstrac
     @SuppressWarnings("unchecked")
     private final Map<TestElement, Customizer> customizers = new LRUMap(20);
 
-    /**
-     * Index of the customizer in the JPanel's child component list:
-     */
+    /** Index of the customizer in the JPanel's child component list: */
     private int customizerIndexInPanel;
 
-    /**
-     * The property name to value map that the active customizer edits:
-     */
+    /** The property name to value map that the active customizer edits: */
     private final Map<String, Object> propertyMap = new HashMap<>();
 
-    /**
-     * Whether the GUI components have been created.
-     */
+    /** Whether the GUI components have been created. */
     private boolean initialized = false;
 
     static {
@@ -168,18 +160,14 @@ public class TestBeanGUI extends Abstrac
             beanInfo = Introspector.getBeanInfo(testBeanClass);
         } catch (IntrospectionException e) {
             log.error("Can't get beanInfo for {}", testBeanClass, e);
-            throw new Error(e.toString()); // Programming error. Don't
-                                            // continue.
+            throw new Error(e); // Programming error. Don't continue.
         }
 
         customizerClass = beanInfo.getBeanDescriptor().getCustomizerClass();
 
-        // 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!
+        // Creation of customizer and GUI initialization
+        // is delayed until the first configure call.
+        // It's not needed to find the static label, menu categories, etc.
         initialized = false;
         JMeterUtils.addLocaleChangeListener(this);
     }
@@ -208,23 +196,22 @@ public class TestBeanGUI extends Abstrac
      * {@inheritDoc}
      */
    @Override
-public TestElement createTestElement() {
+    public TestElement createTestElement() {
         try {
             TestElement element = (TestElement) testBeanClass.newInstance();
             // In other GUI component, clearGUI resets the value to defaults one as there is one GUI per Element
             // With TestBeanGUI as it's shared, its default values are only known here, we must call setValues with 
             // element (as it holds default values)
             // otherwise we will get values as computed by customizer reset and not default ones
-            if(initialized) {
+            if (initialized) {
                 setValues(element);
             }
-            modifyTestElement(element); // put the default values back into the
-            // new element
+            // put the default values back into the new element
+            modifyTestElement(element);
             return element;
         } catch (InstantiationException | IllegalAccessException e) {
             log.error("Can't create test element", e);
-            throw new Error(e.toString()); // Programming error. Don't
-                                            // continue.
+            throw new Error(e); // Programming error. Don't continue.
         }
     }
 
@@ -282,36 +269,22 @@ public TestElement createTestElement() {
      */
     @Override
     public JPopupMenu createPopupMenu() {
-        if (Timer.class.isAssignableFrom(testBeanClass))
-        {
+        if (Timer.class.isAssignableFrom(testBeanClass)) {
             return MenuFactory.getDefaultTimerMenu();
-        }
-        else if(Sampler.class.isAssignableFrom(testBeanClass))
-        {
+        } else if (Sampler.class.isAssignableFrom(testBeanClass)) {
             return MenuFactory.getDefaultSamplerMenu();
-        }
-        else if(ConfigElement.class.isAssignableFrom(testBeanClass))
-        {
+        } else if (ConfigElement.class.isAssignableFrom(testBeanClass)) {
             return MenuFactory.getDefaultConfigElementMenu();
-        }
-        else if(Assertion.class.isAssignableFrom(testBeanClass))
-        {
+        } else if (Assertion.class.isAssignableFrom(testBeanClass)) {
             return MenuFactory.getDefaultAssertionMenu();
-        }
-        else if(PostProcessor.class.isAssignableFrom(testBeanClass) ||
-                PreProcessor.class.isAssignableFrom(testBeanClass))
-        {
+        } else if (PostProcessor.class.isAssignableFrom(testBeanClass)
+                || PreProcessor.class.isAssignableFrom(testBeanClass)) {
             return MenuFactory.getDefaultExtractorMenu();
-        }
-        else if(Visualizer.class.isAssignableFrom(testBeanClass))
-        {
+        } else if (Visualizer.class.isAssignableFrom(testBeanClass)) {
             return MenuFactory.getDefaultVisualizerMenu();
-        }
-        else if(Controller.class.isAssignableFrom(testBeanClass))
-        {
+        } else if (Controller.class.isAssignableFrom(testBeanClass)) {
             return MenuFactory.getDefaultControllerMenu();
-        }
-        else {
+        } else {
             log.warn("Cannot determine PopupMenu for {}", testBeanClass);
             return MenuFactory.getDefaultMenu();
         }
@@ -326,11 +299,8 @@ public TestElement createTestElement() {
             init();
         }
         clearGui();
-
         super.configure(element);
-
         setValues(element);
-
         initialized = true;
     }
     
@@ -364,20 +334,17 @@ public TestElement createTestElement() {
     /** {@inheritDoc} */
     @Override
     public Collection<String> getMenuCategories() {
-        List<String> menuCategories = new LinkedList<>();
         BeanDescriptor bd = beanInfo.getBeanDescriptor();
 
-        // We don't want to show expert beans in the menus unless we're
-        // in expert mode:
+        // Don't show expert beans in the menus unless we're in expert mode
         if (bd.isExpert() && !JMeterUtils.isExpertMode()) {
             return null;
         }
 
-        int matches = setupGuiClasses(menuCategories);
-        if (matches == 0) {
+        List<String> menuCategories = setupGuiClassesList();
+        if (menuCategories.isEmpty()) {
             log.error("Could not assign GUI class to {}", testBeanClass);
-        } else if (matches > 1) {// may be impossible, but no harm in
-                                    // checking ...
+        } else if (menuCategories.size() > 1) {
             log.error("More than 1 GUI class found for {}", testBeanClass);
         }
         return menuCategories;
@@ -385,62 +352,55 @@ public TestElement createTestElement() {
 
     /**
      * Setup GUI class
+     *
      * @return number of matches
      */
     public int setupGuiClasses() {
-        return setupGuiClasses(new ArrayList<String>());
+        return setupGuiClassesList().size();
     }
     
     /**
      * Setup GUI class
-     * @param menuCategories List<String> menu categories
-     * @return number of matches
+     *
+     * @return matches
      */
-    private int setupGuiClasses(List<String> menuCategories ) {
-        int matches = 0;// How many classes can we assign from?
+    private List<String> setupGuiClassesList() {
+        List<String> menuCategories = new ArrayList<>();
         // TODO: there must be a nicer way...
         BeanDescriptor bd = beanInfo.getBeanDescriptor();
         if (Assertion.class.isAssignableFrom(testBeanClass)) {
             menuCategories.add(MenuFactory.ASSERTIONS);
             bd.setValue(TestElement.GUI_CLASS, AbstractAssertionGui.class.getName());
-            matches++;
         }
         if (ConfigElement.class.isAssignableFrom(testBeanClass)) {
             menuCategories.add(MenuFactory.CONFIG_ELEMENTS);
             bd.setValue(TestElement.GUI_CLASS, AbstractConfigGui.class.getName());
-            matches++;
         }
         if (Controller.class.isAssignableFrom(testBeanClass)) {
             menuCategories.add(MenuFactory.CONTROLLERS);
             bd.setValue(TestElement.GUI_CLASS, AbstractControllerGui.class.getName());
-            matches++;
         }
         if (Visualizer.class.isAssignableFrom(testBeanClass)) {
             menuCategories.add(MenuFactory.LISTENERS);
             bd.setValue(TestElement.GUI_CLASS, AbstractVisualizer.class.getName());
-            matches++;
         }
         if (PostProcessor.class.isAssignableFrom(testBeanClass)) {
             menuCategories.add(MenuFactory.POST_PROCESSORS);
             bd.setValue(TestElement.GUI_CLASS, AbstractPostProcessorGui.class.getName());
-            matches++;
         }
         if (PreProcessor.class.isAssignableFrom(testBeanClass)) {
             menuCategories.add(MenuFactory.PRE_PROCESSORS);
             bd.setValue(TestElement.GUI_CLASS, AbstractPreProcessorGui.class.getName());
-            matches++;
         }
         if (Sampler.class.isAssignableFrom(testBeanClass)) {
             menuCategories.add(MenuFactory.SAMPLERS);
             bd.setValue(TestElement.GUI_CLASS, AbstractSamplerGui.class.getName());
-            matches++;
         }
         if (Timer.class.isAssignableFrom(testBeanClass)) {
             menuCategories.add(MenuFactory.TIMERS);
             bd.setValue(TestElement.GUI_CLASS, AbstractTimerGui.class.getName());
-            matches++;
         }
-        return matches;
+        return menuCategories;
     }
 
     private void init() {

Modified: jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java?rev=1818750&r1=1818749&r2=1818750&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/ThreadGroup.java Wed Dec 20 08:50:00 2017
@@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit;
 
 import org.apache.jmeter.engine.StandardJMeterEngine;
 import org.apache.jmeter.engine.TreeCloner;
+import org.apache.jmeter.gui.GUIMenuSortOrder;
 import org.apache.jmeter.testelement.property.BooleanProperty;
 import org.apache.jmeter.testelement.property.IntegerProperty;
 import org.apache.jmeter.testelement.property.LongProperty;
@@ -41,6 +42,7 @@ import org.slf4j.LoggerFactory;
  * 
  * This class is intended to be ThreadSafe.
  */
+@GUIMenuSortOrder(1)
 public class ThreadGroup extends AbstractThreadGroup {
     private static final long serialVersionUID = 282L;
 

Modified: jmeter/trunk/src/core/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java?rev=1818750&r1=1818749&r2=1818750&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/threads/gui/AbstractThreadGroupGui.java Wed Dec 20 08:50:00 2017
@@ -26,6 +26,7 @@ import java.util.Collection;
 import javax.swing.BorderFactory;
 import javax.swing.Box;
 import javax.swing.ButtonGroup;
+import javax.swing.JMenu;
 import javax.swing.JMenuItem;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
@@ -45,13 +46,9 @@ public abstract class AbstractThreadGrou
 
     // Sampler error action buttons
     private JRadioButton continueBox;
-
     private JRadioButton startNextLoop;
-
-    private JRadioButton stopThrdBox;
-
+    private JRadioButton stopThreadBox;
     private JRadioButton stopTestBox;
-
     private JRadioButton stopTestNowBox;
 
     public AbstractThreadGroupGui(){
@@ -68,48 +65,15 @@ public abstract class AbstractThreadGrou
     @Override
     public JPopupMenu createPopupMenu() {
         JPopupMenu pop = new JPopupMenu();
-        pop.add(MenuFactory.makeMenus(new String[] {
-                MenuFactory.CONTROLLERS,
-                MenuFactory.CONFIG_ELEMENTS,
-                MenuFactory.TIMERS,
-                MenuFactory.PRE_PROCESSORS,
-                MenuFactory.SAMPLERS,
-                MenuFactory.POST_PROCESSORS,
-                MenuFactory.ASSERTIONS,
-                MenuFactory.LISTENERS,
-                },
-                JMeterUtils.getResString("add"), // $NON-NLS-1$
-                ActionNames.ADD));
-        
-        if(this.isEnabled() && 
-                // Check test is not started already
-                !JMeterUtils.isTestRunning()) {
-            pop.addSeparator();
+        pop.add(createAddMenu());
 
-            JMenuItem addThinkTimesToChildren = new JMenuItem(JMeterUtils.getResString("add_think_times"));
-            addThinkTimesToChildren.setName("add_think_times");
-            addThinkTimesToChildren.addActionListener(ActionRouter.getInstance());
-            addThinkTimesToChildren.setActionCommand(ActionNames.ADD_THINK_TIME_BETWEEN_EACH_STEP);
-            pop.add(addThinkTimesToChildren);
-
-            JMenuItem runTg = new JMenuItem(JMeterUtils.getResString("run_threadgroup"));
-            runTg.setName("run_threadgroup");
-            runTg.addActionListener(ActionRouter.getInstance());
-            runTg.setActionCommand(ActionNames.RUN_TG);
-            pop.add(runTg);
-    
-            JMenuItem runTgNotimers = new JMenuItem(JMeterUtils.getResString("run_threadgroup_no_timers"));
-            runTgNotimers.setName("run_threadgroup_no_timers");
-            runTgNotimers.addActionListener(ActionRouter.getInstance());
-            runTgNotimers.setActionCommand(ActionNames.RUN_TG_NO_TIMERS);
-            pop.add(runTgNotimers);
-
-            JMenuItem validateTg = new JMenuItem(JMeterUtils.getResString("validate_threadgroup"));
-            validateTg.setName("validate_threadgroup");
-            validateTg.addActionListener(ActionRouter.getInstance());
-            validateTg.setActionCommand(ActionNames.VALIDATE_TG);
-            pop.add(validateTg);
+        if (this.isEnabled() && !JMeterUtils.isTestRunning()) {
+            pop.addSeparator();
 
+            pop.add(createMenuItem("add_think_times", ActionNames.ADD_THINK_TIME_BETWEEN_EACH_STEP));
+            pop.add(createMenuItem("run_threadgroup", ActionNames.RUN_TG));
+            pop.add(createMenuItem("run_threadgroup_no_timers", ActionNames.RUN_TG_NO_TIMERS));
+            pop.add(createMenuItem("validate_threadgroup", ActionNames.VALIDATE_TG));
         }
         
         MenuFactory.addEditMenu(pop, true);
@@ -117,6 +81,32 @@ public abstract class AbstractThreadGrou
         return pop;
     }
 
+    private JMenuItem createMenuItem(String name, String actionCommand) {
+        JMenuItem addThinkTimesToChildren = new JMenuItem(JMeterUtils.getResString(name));
+        addThinkTimesToChildren.setName(name);
+        addThinkTimesToChildren.addActionListener(ActionRouter.getInstance());
+        addThinkTimesToChildren.setActionCommand(actionCommand);
+        return addThinkTimesToChildren;
+    }
+
+    private JMenu createAddMenu() {
+        String addAction = ActionNames.ADD;
+        JMenu addMenu = new JMenu(JMeterUtils.getResString("add")); // $NON-NLS-1$
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.SAMPLERS, addAction));
+        addMenu.addSeparator();
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.PRE_PROCESSORS, addAction));
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.POST_PROCESSORS, addAction));
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.ASSERTIONS, addAction));
+        addMenu.addSeparator();
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.TIMERS, addAction));
+        addMenu.addSeparator();
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.FRAGMENTS, addAction));
+        addMenu.addSeparator();
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.CONFIG_ELEMENTS, addAction));
+        addMenu.add(MenuFactory.makeMenu(MenuFactory.LISTENERS, addAction));
+        return addMenu;
+    }
+
     @Override
     public Dimension getPreferredSize() {
         return getMinimumSize();
@@ -128,7 +118,8 @@ public abstract class AbstractThreadGrou
         initGui();
     }
 
-    private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final)
+    // WARNING: called from ctor so must not be overridden (i.e. must be private or final)
+    private void init() {
         setLayout(new BorderLayout(0, 5));
         setBorder(makeBorder());
 
@@ -144,27 +135,33 @@ public abstract class AbstractThreadGrou
 
     private JPanel createOnErrorPanel() {
         JPanel panel = new JPanel();
-        panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("sampler_on_error_action"))); // $NON-NLS-1$
+        panel.setBorder(BorderFactory.createTitledBorder(
+                JMeterUtils.getResString("sampler_on_error_action"))); // $NON-NLS-1$
 
         ButtonGroup group = new ButtonGroup();
 
-        continueBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_continue")); // $NON-NLS-1$
+        continueBox = new JRadioButton(
+                JMeterUtils.getResString("sampler_on_error_continue")); // $NON-NLS-1$
         group.add(continueBox);
         panel.add(continueBox);
 
-        startNextLoop = new JRadioButton(JMeterUtils.getResString("sampler_on_error_start_next_loop")); // $NON-NLS-1$
+        startNextLoop = new JRadioButton(
+                JMeterUtils.getResString("sampler_on_error_start_next_loop")); // $NON-NLS-1$
         group.add(startNextLoop);
         panel.add(startNextLoop);
 
-        stopThrdBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_thread")); // $NON-NLS-1$
-        group.add(stopThrdBox);
-        panel.add(stopThrdBox);
+        stopThreadBox = new JRadioButton(
+                JMeterUtils.getResString("sampler_on_error_stop_thread")); // $NON-NLS-1$
+        group.add(stopThreadBox);
+        panel.add(stopThreadBox);
 
-        stopTestBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test")); // $NON-NLS-1$
+        stopTestBox = new JRadioButton(
+                JMeterUtils.getResString("sampler_on_error_stop_test")); // $NON-NLS-1$
         group.add(stopTestBox);
         panel.add(stopTestBox);
 
-        stopTestNowBox = new JRadioButton(JMeterUtils.getResString("sampler_on_error_stop_test_now")); // $NON-NLS-1$
+        stopTestNowBox = new JRadioButton(
+                JMeterUtils.getResString("sampler_on_error_stop_test_now")); // $NON-NLS-1$
         group.add(stopTestNowBox);
         panel.add(stopTestNowBox);
 
@@ -177,7 +174,7 @@ public abstract class AbstractThreadGrou
         } else if (te.getOnErrorStopTestNow()) {
             stopTestNowBox.setSelected(true);
         } else if (te.getOnErrorStopThread()) {
-            stopThrdBox.setSelected(true);
+            stopThreadBox.setSelected(true);
         } else if (te.getOnErrorStartNextLoop()) {
             startNextLoop.setSelected(true);
         } else {
@@ -192,7 +189,7 @@ public abstract class AbstractThreadGrou
         if (stopTestNowBox.isSelected()) {
             return AbstractThreadGroup.ON_SAMPLE_ERROR_STOPTEST_NOW;
         }
-        if (stopThrdBox.isSelected()) {
+        if (stopThreadBox.isSelected()) {
             return AbstractThreadGroup.ON_SAMPLE_ERROR_STOPTHREAD;
         }
         if (startNextLoop.isSelected()) {