/*
* 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;
import java.awt.Component;
import java.awt.HeadlessException;
import java.io.IOException;
import java.io.Serializable;
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.Set;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.MenuElement;
import org.apache.jmeter.control.Controller;
import org.apache.jmeter.gui.GuiPackage;
import org.apache.jmeter.gui.JMeterGUIComponent;
import org.apache.jmeter.gui.UndoHistory;
import org.apache.jmeter.gui.action.ActionNames;
import org.apache.jmeter.gui.action.ActionRouter;
import org.apache.jmeter.gui.action.KeyStrokes;
import org.apache.jmeter.gui.tree.JMeterTreeNode;
import org.apache.jmeter.samplers.Sampler;
import org.apache.jmeter.testbeans.TestBean;
import org.apache.jmeter.testbeans.gui.TestBeanGUI;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.testelement.WorkBench;
import org.apache.jmeter.util.JMeterUtils;
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;
public final class MenuFactory {
private static final Logger log = LoggerFactory.getLogger(MenuFactory.class);
/*
* Predefined strings for makeMenu().
* These are used as menu categories in the menuMap Hashmap,
* and also for resource lookup in messages.properties
*/
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$
private static final Map<String, List<MenuInfo>> menuMap = new HashMap<>();
private static final Set<String> elementsToSkip = new HashSet<>();
// 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;
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);
try {
String[] classesToSkip =
JOrphanUtils.split(JMeterUtils.getPropDefault("not_in_menu", ""), ","); //$NON-NLS-1$
for (String aClassesToSkip : classesToSkip) {
elementsToSkip.add(aClassesToSkip.trim());
}
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 constructor to prevent instantiation.
*/
private MenuFactory() {
}
public static void addEditMenu(JPopupMenu menu, boolean removable) {
addSeparator(menu);
if (removable) {
menu.add(makeMenuItemRes("cut", ActionNames.CUT, KeyStrokes.CUT)); //$NON-NLS-1$
}
menu.add(makeMenuItemRes("copy", ActionNames.COPY, KeyStrokes.COPY)); //$NON-NLS-1$
menu.add(makeMenuItemRes("paste", ActionNames.PASTE, KeyStrokes.PASTE)); //$NON-NLS-1$
menu.add(makeMenuItemRes("duplicate", ActionNames.DUPLICATE, KeyStrokes.DUPLICATE)); //$NON-NLS-1$
if (removable) {
menu.add(makeMenuItemRes("remove", ActionNames.REMOVE, KeyStrokes.REMOVE)); //$NON-NLS-1$
}
}
public static void addPasteResetMenu(JPopupMenu menu) {
addSeparator(menu);
menu.add(makeMenuItemRes("paste", ActionNames.PASTE, KeyStrokes.PASTE)); //$NON-NLS-1$
}
public static void addFileMenu(JPopupMenu pop) {
addFileMenu(pop, true);
}
/**
* @param menu JPopupMenu
* @param addSaveTestFragmentMenu Add Save as Test Fragment menu if true
*/
public static void addFileMenu(JPopupMenu menu, boolean addSaveTestFragmentMenu) {
// the undo/redo as a standard goes first in Edit menus
// maybe there's better place for them in JMeter?
if(UndoHistory.isEnabled()) {
addUndoItems(menu);
}
addSeparator(menu);
menu.add(makeMenuItemRes("open", ActionNames.OPEN));// $NON-NLS-1$
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$
}
addSeparator(menu);
JMenuItem savePicture = makeMenuItemRes("save_as_image",// $NON-NLS-1$
ActionNames.SAVE_GRAPHICS,
KeyStrokes.SAVE_GRAPHICS);
menu.add(savePicture);
if (!(GuiPackage.getInstance().getCurrentGui() instanceof Printable)) {
savePicture.setEnabled(false);
}
JMenuItem savePictureAll = makeMenuItemRes("save_as_image_all",// $NON-NLS-1$
ActionNames.SAVE_GRAPHICS_ALL,
KeyStrokes.SAVE_GRAPHICS_ALL);
menu.add(savePictureAll);
addSeparator(menu);
JMenuItem disabled = makeMenuItemRes("disable", ActionNames.DISABLE);// $NON-NLS-1$
JMenuItem enabled = makeMenuItemRes("enable", ActionNames.ENABLE);// $NON-NLS-1$
boolean isEnabled = GuiPackage.getInstance().getTreeListener().getCurrentNode().isEnabled();
if (isEnabled) {
disabled.setEnabled(true);
enabled.setEnabled(false);
} else {
disabled.setEnabled(false);
enabled.setEnabled(true);
}
menu.add(enabled);
menu.add(disabled);
JMenuItem toggle = makeMenuItemRes("toggle", ActionNames.TOGGLE, KeyStrokes.TOGGLE);// $NON-NLS-1$
menu.add(toggle);
addSeparator(menu);
menu.add(makeMenuItemRes("help", ActionNames.HELP));// $NON-NLS-1$
}
/**
* Add undo / redo
* @param menu JPopupMenu
*/
private static void addUndoItems(JPopupMenu menu) {
addSeparator(menu);
JMenuItem undo = makeMenuItemRes("undo", ActionNames.UNDO); //$NON-NLS-1$
undo.setEnabled(GuiPackage.getInstance().canUndo());
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)
redo.setEnabled(GuiPackage.getInstance().canRedo());
menu.add(redo);
}
public static JMenu makeMenus(String[] categories, String label, String actionCommand) {
JMenu addMenu = new JMenu(label);
for (String category : categories) {
addMenu.add(makeMenu(category, actionCommand));
}
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(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,
JMeterUtils.getResString("change_parent"),// $NON-NLS-1$
ActionNames.CHANGE_PARENT));
pop.add(makeMenus(MENU_PARENT_CONTROLLER,
JMeterUtils.getResString("insert_parent"),// $NON-NLS-1$
ActionNames.ADD_PARENT));
MenuFactory.addEditMenu(pop, true);
MenuFactory.addFileMenu(pop);
return pop;
}
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,
JMeterUtils.getResString("insert_parent"),// $NON-NLS-1$
ActionNames.ADD_PARENT));
MenuFactory.addEditMenu(pop, true);
MenuFactory.addFileMenu(pop);
return pop;
}
public static JPopupMenu getDefaultConfigElementMenu() {
JPopupMenu pop = new JPopupMenu();
MenuFactory.addEditMenu(pop, true);
MenuFactory.addFileMenu(pop);
return pop;
}
public static JPopupMenu getDefaultVisualizerMenu() {
JPopupMenu pop = new JPopupMenu();
pop.add(
MenuFactory.makeMenuItemRes("clear", ActionNames.CLEAR)); //$NON-NLS-1$
MenuFactory.addEditMenu(pop, true);
MenuFactory.addFileMenu(pop);
return pop;
}
public static JPopupMenu getDefaultTimerMenu() {
JPopupMenu pop = new JPopupMenu();
MenuFactory.addEditMenu(pop, true);
MenuFactory.addFileMenu(pop);
return pop;
}
public static JPopupMenu getDefaultAssertionMenu() {
JPopupMenu pop = new JPopupMenu();
MenuFactory.addEditMenu(pop, true);
MenuFactory.addFileMenu(pop);
return pop;
}
public static JPopupMenu getDefaultExtractorMenu() {
JPopupMenu pop = new JPopupMenu();
MenuFactory.addEditMenu(pop, true);
MenuFactory.addFileMenu(pop);
return pop;
}
public static JPopupMenu getDefaultMenu() { // if type is unknown
JPopupMenu pop = new JPopupMenu();
MenuFactory.addEditMenu(pop, true);
MenuFactory.addFileMenu(pop);
return pop;
}
/**
* 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
* @return the menu
*/
public static JMenu makeMenu(String category, String actionCommand) {
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 menuName The name of the newly created menu
* @return the menu
*/
public static JMenu makeMenu(Collection<MenuInfo> menuInfo, String actionCommand, String menuName) {
JMenu menu = new JMenu(menuName);
for (MenuInfo info : menuInfo) {
menu.add(makeMenuItem(info, actionCommand));
}
GuiUtils.makeScrollableMenu(menu);
return menu;
}
public static void setEnabled(JMenu menu) {
if (menu.getSubElements().length == 0) {
menu.setEnabled(false);
}
}
/**
* Create a single menu item
*
* @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
* @return the menu item
*/
public static JMenuItem makeMenuItem(String label, String name, String actionCommand) {
JMenuItem newMenuChoice = new JMenuItem(label);
newMenuChoice.setName(name);
newMenuChoice.addActionListener(ActionRouter.getInstance());
if (actionCommand != null) {
newMenuChoice.setActionCommand(actionCommand);
}
return newMenuChoice;
}
/**
* 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
* @return the menu item
*/
public static JMenuItem makeMenuItemRes(String resource, String actionCommand) {
JMenuItem newMenuChoice = new JMenuItem(JMeterUtils.getResString(resource));
newMenuChoice.setName(resource);
newMenuChoice.addActionListener(ActionRouter.getInstance());
if (actionCommand != null) {
newMenuChoice.setActionCommand(actionCommand);
}
return newMenuChoice;
}
/**
* 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
* @return the menu item
*/
public static Component makeMenuItem(MenuInfo info, String actionCommand) {
JMenuItem newMenuChoice = new JMenuItem(info.getLabel());
newMenuChoice.setName(info.getClassName());
newMenuChoice.addActionListener(ActionRouter.getInstance());
if (actionCommand != null) {
newMenuChoice.setActionCommand(actionCommand);
}
return newMenuChoice;
}
public 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) {
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 });
Collections.sort(guiClasses);
for (String name : guiClasses) {
/*
* JMeterTreeNode and TestBeanGUI are special GUI classes, and
* aren't intended to be added to menus
*
* TODO: find a better way of checking this
*/
if (name.endsWith("JMeterTreeNode") // $NON-NLS-1$
|| name.endsWith("TestBeanGUI")) {// $NON-NLS-1$
continue;// Don't try to instantiate these
}
if (elementsToSkip.contains(name)) { // No point instantiating class
log.info("Skipping {}", name);
continue;
}
boolean hideBean = false; // Should the TestBean be hidden?
JMeterGUIComponent item = null;
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, e);
continue;
} catch(HeadlessException e) {
log.warn("Could not instantiate class: {}", name, e); // NOSONAR
continue;
} catch(RuntimeException e) {
throw e;
} catch (Exception e) {
log.warn("Could not instantiate class: {}", name, e); // NOSONAR
continue;
}
if (hideBean || elementsToSkip.contains(item.getStaticLabel())) {
log.info("Skipping {}", name);
continue;
} else {
elementsToSkip.add(name); // Don't add it again
}
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 void addSeparator(JPopupMenu menu) {
MenuElement[] elements = menu.getSubElements();
if ((elements.length > 0) && !(elements[elements.length - 1] instanceof JPopupMenu.Separator)) {
menu.addSeparator();
}
}
/**
* Determine whether or not nodes can be added to this parent.
*
* 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
* @return whether it is OK to add the element to this parent
*/
public static boolean canAddTo(JMeterTreeNode parentNode, TestElement element) {
JMeterTreeNode node = new JMeterTreeNode(element, null);
return canAddTo(parentNode, new JMeterTreeNode[]{node});
}
/**
* Determine whether or not nodes can be added to this parent.
*
* Used by DragNDrop and Paste.
*
* @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
* @return whether it is OK to add the dragged nodes to this parent
*/
public static boolean canAddTo(JMeterTreeNode parentNode, JMeterTreeNode[] nodes) {
if (null == parentNode) {
return false;
}
if (foundClass(nodes, new Class[]{WorkBench.class})){// Can't add a Workbench anywhere
return false;
}
if (foundClass(nodes, new Class[]{TestPlan.class})){// Can't add a TestPlan anywhere
return false;
}
TestElement parent = parentNode.getTestElement();
// Force TestFragment to only be pastable under a Test Plan
if (foundClass(nodes, new Class[]{org.apache.jmeter.control.TestFragmentController.class})){
if (parent instanceof TestPlan) {
return true;
}
return false;
}
if (parent instanceof WorkBench) {// allow everything else
return true;
}
if (parent instanceof TestPlan) {
if (foundClass(nodes,
new Class[]{Sampler.class, Controller.class}, // Samplers and Controllers need not apply ...
org.apache.jmeter.threads.AbstractThreadGroup.class) // but AbstractThreadGroup (Controller) is OK
){
return false;
}
return true;
}
// AbstractThreadGroup is only allowed under a TestPlan
if (foundClass(nodes, new Class[]{org.apache.jmeter.threads.AbstractThreadGroup.class})){
return false;
}
if (parent instanceof Controller) {// Includes thread group; anything goes
return true;
}
if (parent instanceof Sampler) {// Samplers and Controllers need not apply ...
if (foundClass(nodes, new Class[]{Sampler.class, Controller.class})){
return false;
}
return true;
}
// All other
return false;
}
// Is any node an instance of one of the classes?
private static boolean foundClass(JMeterTreeNode[] nodes, Class<?>[] classes) {
for (JMeterTreeNode node : nodes) {
for (Class<?> aClass : classes) {
if (aClass.isInstance(node.getUserObject())) {
return true;
}
}
}
return false;
}
// Is any node an instance of one of the classes, but not an exception?
private static boolean foundClass(JMeterTreeNode[] nodes, Class<?>[] classes, Class<?> except) {
for (JMeterTreeNode node : nodes) {
Object userObject = node.getUserObject();
if (!except.isInstance(userObject)) {
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()){
Collections.sort(me.getValue(), new MenuInfoComparator(!me.getKey().equals(THREADS)));
}
}
}