blob: 92d825fb89a63aae67cd88193c3f79c2adaaf28b [file] [log] [blame]
/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.JMenu;
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;
import org.apache.jmeter.gui.GuiPackage;
import org.apache.jmeter.gui.JMeterGUIComponent;
import org.apache.jmeter.gui.TestElementMetadata;
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.menu.StaticJMeterGUIComponent;
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.NonTestElement;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.threads.AbstractThreadGroup;
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.jetbrains.annotations.VisibleForTesting;
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
* 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;
static {
menuMap = new HashMap<>();
menuMap.put(THREADS, new ArrayList<>());
menuMap.put(TIMERS, new ArrayList<>());
menuMap.put(ASSERTIONS, new ArrayList<>());
menuMap.put(CONFIG_ELEMENTS, new ArrayList<>());
menuMap.put(CONTROLLERS, new ArrayList<>());
menuMap.put(LISTENERS, new ArrayList<>());
menuMap.put(NON_TEST_ELEMENTS, new ArrayList<>());
menuMap.put(SAMPLERS, new ArrayList<>());
menuMap.put(POST_PROCESSORS, new ArrayList<>());
menuMap.put(PRE_PROCESSORS, new ArrayList<>());
menuMap.put(FRAGMENTS, new ArrayList<>());
menuMap.put(SEPARATOR, Collections.singletonList(new MenuSeparatorInfo()));
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);
}
}
@VisibleForTesting
static Map<String, List<MenuInfo>> getMenuMap() {
return menuMap;
}
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 {
// TODO: migrate to ServiceLoader or something else
@SuppressWarnings("deprecation")
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 -> !name.equals("org.apache.jmeter.gui.menu.StaticJMeterGUIComponent"))
.filter(name -> !elementsToSkip.contains(name))
.distinct()
.map(String::trim)
.collect(Collectors.toList());
boolean debugTimings = log.isDebugEnabled();
Map<String, Long> times = debugTimings ? new HashMap<>() : null;
Map<String, JMeterGUIComponent> comps = debugTimings ? new HashMap<>() : null;
long a0 = System.currentTimeMillis();
for (String className : guiClasses) {
long t0 = 0;
if (debugTimings) {
t0 = System.currentTimeMillis();
}
JMeterGUIComponent item = getGUIComponent(className, elementsToSkip);
if (debugTimings) {
long t1 = System.currentTimeMillis();
times.put(className, t1 - t0);
comps.put(className, item);
}
if (item == null) {
continue;
}
Collection<String> categories = item.getMenuCategories();
if (categories == null) {
log.debug("{} participates in no menus.", className);
continue;
}
for (Map.Entry<String, List<MenuInfo>> entry: menus.entrySet()) {
if (categories.contains(entry.getKey())) {
entry.getValue().add(new MenuInfo(item, className));
}
}
}
if (debugTimings) {
long a1 = System.currentTimeMillis();
times.entrySet().stream()
.sorted(Comparator.comparingLong(Map.Entry::getValue))
.forEachOrdered(e -> {
String res = "";
JMeterGUIComponent comp = comps.get(e.getKey());
if (comp != null && comp.getLabelResource() != null) {
res = " @TestElementMetadata(labelResource = \""
+ comp.getLabelResource() + "\")";
}
log.debug("{}ms {} {}", e.getValue(), e.getKey(), res);
});
log.debug("{}ms total menu initialization time", a1 - a0);
}
} catch (IOException e) {
log.error("IO Exception while initializing menus.", e);
}
}
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, false, MenuFactory.class.getClassLoader());
TestElementMetadata metadata = c.getAnnotation(TestElementMetadata.class);
if (metadata != null) {
item = new StaticJMeterGUIComponent(c, metadata);
} else if (TestBean.class.isAssignableFrom(c)) {
TestBeanGUI testBeanGUI = new TestBeanGUI(c);
hideBean = testBeanGUI.isHidden()
|| (testBeanGUI.isExpert() && !JMeterUtils.isExpertMode());
item = testBeanGUI;
} else {
item = (JMeterGUIComponent) c.getDeclaredConstructor().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<? extends 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<? extends 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());
}
}
}
/**
* 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
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", // $NON-NLS-1$
ActionNames.SAVE_AS_TEST_FRAGMENT));
}
addSeparator(menu);
JMenuItem saveKotlinDsl = makeMenuItemRes("copy_code", // $NON-NLS-1$
ActionNames.COPY_CODE);
menu.add(saveKotlinDsl);
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();
disabled.setEnabled(isEnabled);
enabled.setEnabled(!isEnabled);
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 to the provided menu
*
* @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 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);
Arrays.stream(categories)
.map(category -> makeMenu(category, actionCommand))
.forEach(addMenu::add);
GuiUtils.makeScrollableMenu(addMenu);
return addMenu;
}
public static JPopupMenu getDefaultControllerMenu() {
JPopupMenu pop = new JPopupMenu();
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.CONTROLLERS, addAction));
addMenu.addSeparator();
pop.add(addDefaultAddMenuToMenu(addMenu, addAction));
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(new String[]{CONTROLLERS},
JMeterUtils.getResString("change_parent"),// $NON-NLS-1$
ActionNames.CHANGE_PARENT));
pop.add(makeMenus(new String[]{CONTROLLERS},
JMeterUtils.getResString("insert_parent"),// $NON-NLS-1$
ActionNames.ADD_PARENT));
MenuFactory.addEditMenu(pop, true);
MenuFactory.addFileMenu(pop);
return pop;
}
@VisibleForTesting
static JMenu createDefaultAddMenu() {
String addAction = ActionNames.ADD;
JMenu addMenu = new JMenu(JMeterUtils.getResString("add")); // $NON-NLS-1$
addDefaultAddMenuToMenu(addMenu, addAction);
return addMenu;
}
private static JMenu addDefaultAddMenuToMenu(JMenu addMenu, String addAction) {
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(createDefaultAddMenu());
pop.add(makeMenus(new String[]{CONTROLLERS},
JMeterUtils.getResString("insert_parent"),// $NON-NLS-1$
ActionNames.ADD_PARENT));
MenuFactory.addEditMenu(pop, true);
MenuFactory.addFileMenu(pop);
return pop;
}
public static JPopupMenu getDefaultConfigElementMenu() {
return createDefaultPopupMenu();
}
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() {
return createDefaultPopupMenu();
}
public static JPopupMenu getDefaultAssertionMenu() {
return createDefaultPopupMenu();
}
public static JPopupMenu getDefaultExtractorMenu() {
return createDefaultPopupMenu();
}
public static JPopupMenu getDefaultMenu() { // if type is unknown
return createDefaultPopupMenu();
}
private static JPopupMenu createDefaultPopupMenu() {
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. {@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));
}
/**
* Create a menu from a collection of items.
*
* @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
*/
private static JMenu makeMenu(
Collection<? extends MenuInfo> menuInfo, String actionCommand, String menuName) {
JMenu menu = new JMenu(menuName);
menuInfo.stream()
.map(info -> makeMenuItem(info, actionCommand))
.forEach(menu::add);
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
* {@link 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
* {@link 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
* {@link ActionNames}
* @return the menu item
*/
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.setEnabled(info.getEnabled(actionCommand));
newMenuChoice.addActionListener(ActionRouter.getInstance());
if (actionCommand != null) {
newMenuChoice.setActionCommand(actionCommand);
}
return newMenuChoice;
}
private static JMenuItem makeMenuItemRes(String resource, String actionCommand, KeyStroke accel) {
JMenuItem item = makeMenuItemRes(resource, actionCommand);
item.setAccelerator(accel);
return item;
}
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.
* <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
* @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.
* <p>
* 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 (parentNode == null
|| foundClass(nodes, new Class[]{TestPlan.class})) {
return false;
}
TestElement parent = parentNode.getTestElement();
// Force TestFragment to only be pastable under a Test Plan
if (foundClass(nodes, new Class[]{TestFragmentController.class})) {
return parent instanceof TestPlan;
}
// Cannot move Non-Test Elements from root of Test Plan or Test Fragment
if (foundMenuCategories(nodes, NON_TEST_ELEMENTS)
&& !(parent instanceof TestPlan || parent instanceof TestFragmentController)) {
return false;
}
if (parent instanceof TestPlan) {
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;
}
// Includes thread group; anything goes
if (parent instanceof Controller) {
return true;
}
// No Samplers and Controllers
if (parent instanceof Sampler) {
return !foundClass(nodes, new Class[]{Sampler.class, Controller.class});
}
// All other
return false;
}
/**
* 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) {
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 menu category?
* @param nodes Array of {@link JMeterTreeNode}
* @param category Category
* @return true if nodes is in category
*/
private static boolean foundMenuCategories(JMeterTreeNode[] nodes, String category) {
return Arrays.stream(nodes)
.flatMap(node -> node.getMenuCategories().stream())
.anyMatch(category::equals);
}
/**
* 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, 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)));
}
}