/* * Zed Attack Proxy (ZAP) and its related class files. * * ZAP is an HTTP/HTTPS proxy for assessing web application security. * * Licensed 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.zaproxy.zap.extension; import java.awt.Component; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.swing.JMenu; import org.parosproxy.paros.control.Control; import org.zaproxy.zap.view.messagecontainer.MessageContainer; import org.zaproxy.zap.view.popup.ExtensionPopupMenuComponent; import org.zaproxy.zap.view.popup.PopupMenuUtils; public class ExtensionPopupMenu extends JMenu implements ExtensionPopupMenuComponent { private static final long serialVersionUID = 1925623776527543421L; /** * Flag that indicates if the children should be ordered. * * @see #setOrderChildren(boolean) * @see #processExtensionPopupChildren(PopupMenuUtils.PopupMenuInvokerWrapper) */ private boolean orderChildren; public ExtensionPopupMenu() { super(); } public ExtensionPopupMenu(String label) { super(label); } /** * Sets whether or not the menu index of the child components, returned by the method * {@code ExtensionPopupMenuComponent#getMenuIndex()}, should be honoured. * <p> * Default is {@code false}. * </p> * <p> * The child components will be ordered from minor to greater index (starting at index 0 from top of the menu). If two or * more child components have the same index, the index that those child components will have, between each other, is * undefined. * </p> * <p> * Note: Separators added manually (with {@code ExtensionPopupMenu#addSeparator()} or * {@code ExtensionPopupMenu#addSeparator(int)} are not preserved between reorderings. * </p> * * @param orderChildren {@code true} if the child components should ordered, {@code false} otherwise. * @see #isOrderChildren() * @see #processExtensionPopupChildren(PopupMenuUtils.PopupMenuInvokerWrapper) * @see ExtensionPopupMenuComponent#getMenuIndex() * @see ExtensionPopupMenu#addSeparator() * @see ExtensionPopupMenu#insertSeparator(int) */ public void setOrderChildren(boolean orderChildren) { this.orderChildren = orderChildren; } /** * Tells whether or not the menu index of the child components, returned by the method * {@code ExtensionPopupMenuComponent#getMenuIndex()}, should be honoured. * * @return {@code true} if the child components will be ordered, {@code false} otherwise. * @see #setOrderChildren(boolean) */ public boolean isOrderChildren() { return orderChildren; } /** * By default, the pop up menu button is enabled and the pop up menu is only enable for the given {@code invoker} if at * least one of the child menus and menu items is enable for the given {@code invoker}. * <p> * Although the pop up menu is allowed to contain child menus and menu items of any type of {@code JMenu} or * {@code JMenuItem} the only children considered as enablers are the ones of the type of * {@code ExtensionPopupMenuComponent}. * </p> * <p> * The {@code ExtensionPopupMenuComponent}s are considered enable if the corresponding method * {@code isEnableForComponent(Component)}, with {@code invoker} as parameter, returns {@code true}. * </p> * <p> * All the child menus that implement {@code ExtensionPopupMenuComponent} will have the methods * {@code precedeWithSeparator()}, {@code succeedWithSeparator()}, {@code getMenuIndex()} and {@code isSafe()} honoured, * with the following caveats: * <ul> * <li>{@code precedeWithSeparator()} - the separator will only be added if there's already a menu component in the menu and * if it is not a separator;</li> * <li>{@code succeedWithSeparator()} - the separator will be added always but removed if there's no item following it when * the menu is ready to be shown;</li> * <li>{@code getMenuIndex()} - the menu index will be honoured only if the method {@code isOrderChildren()} returns * {@code true};</li> * </ul> * The separators will be dynamically added and removed as needed when the pop up menu is shown. * <p> * <strong>Implementation Note:</strong> The method {@code isEnableForComponent(Component)} is called on all child * {@code ExtensionPopupMenuComponent}s, even if a previous child has returned {@code true}, as it allows to notify all the * children that the pop up menu in which they are, is being invoked. Subclasses should take it into account when overriding * this the method. * </p> * * @see #processExtensionPopupChildren(PopupMenuUtils.PopupMenuInvokerWrapper) * @see ExtensionPopupMenuComponent#isEnableForComponent(Component) * @see #isEnableForMessageContainer(MessageContainer) */ @Override public boolean isEnableForComponent(Component invoker) { return processExtensionPopupChildren(PopupMenuUtils.getPopupMenuInvokerWrapper(invoker)); } /** * Returns {@code true} if at least one of the child {@code ExtensionPopupMenuComponent}s is enable, {@code false} * otherwise. * <p> * The method {@code isEnableForComponent(Component)} or {@code isEnableForMessageContainer(MessageContainer)}, depending on * actual implementation of the given {@code invokerWrapper}, is called on all child {@code ExtensionPopupMenuComponent}s. * </p> * <p> * All the child menus that implement {@code ExtensionPopupMenuComponent} will have the methods * {@code precedeWithSeparator()}, {@code succeedWithSeparator()}, {@code getMenuIndex()} and {@code isSafe()} honoured, * with the following caveats: * <ul> * <li>{@code precedeWithSeparator()} - the separator will only be added if there's already a menu component in the menu and * if it is not a separator;</li> * <li>{@code succeedWithSeparator()} - the separator will be added always but removed if there's no item following it when * the menu is ready to be shown;</li> * <li>{@code getMenuIndex()} - the menu index will be honoured only if the method {@code isOrderChildren()} returns * {@code true};</li> * </ul> * The separators will be dynamically added and removed as needed when the pop up menu is shown. * * @param invokerWrapper the wrapped invoker * @return {@code true} if at least one of the child items is enable, {@code false} otherwise. * @see #isOrderChildren() * @see ExtensionPopupMenuComponent * @see ExtensionPopupMenuComponent#isEnableForComponent(Component) * @see ExtensionPopupMenuComponent#isEnableForMessageContainer(MessageContainer) * @see ExtensionPopupMenuComponent#precedeWithSeparator() * @see ExtensionPopupMenuComponent#succeedWithSeparator() * @see ExtensionPopupMenuComponent#isSafe() * @see ExtensionPopupMenuComponent#getMenuIndex() */ protected boolean processExtensionPopupChildren(PopupMenuUtils.PopupMenuInvokerWrapper invokerWrapper) { if (isOrderChildren()) { PopupMenuUtils.removeAllSeparators(this); List<ExtensionPopupMenuComponent> components = new ArrayList<>(); for (int i = 0; i < getMenuComponentCount(); i++) { Component component = getMenuComponent(i); if (PopupMenuUtils.isExtensionPopupMenuComponent(component)) { ExtensionPopupMenuComponent menuComponent = (ExtensionPopupMenuComponent) component; if (menuComponent.getMenuIndex() >= 0) { components.add(menuComponent); remove(i); i--; } } } Collections.sort(components, new Comparator<ExtensionPopupMenuComponent>() { @Override public int compare(ExtensionPopupMenuComponent component, ExtensionPopupMenuComponent otherComponent) { if (component.getMenuIndex() > otherComponent.getMenuIndex()) { return 1; } else if (component.getMenuIndex() < otherComponent.getMenuIndex()) { return -1; } return 0; }; }); for (int i = 0; i < components.size(); i++) { ExtensionPopupMenuComponent component = components.get(i); int index = Math.max(component.getMenuIndex(), i); if (index >= getMenuComponentCount()) { add((Component) component); } else { getPopupMenu().insert((Component) component, index); } } } boolean childEnable = false; Control.Mode mode = Control.getSingleton().getMode(); for (int i = 0; i < getMenuComponentCount(); ++i) { Component menuComponent = getMenuComponent(i); if (PopupMenuUtils.isExtensionPopupMenuComponent(menuComponent)) { ExtensionPopupMenuComponent extensionMenuComponent = (ExtensionPopupMenuComponent) menuComponent; boolean enable = invokerWrapper.isEnable(extensionMenuComponent); if (enable && !extensionMenuComponent.isSafe() && mode.equals(Control.Mode.safe)) { menuComponent.setEnabled(false); continue; } if (enable) { childEnable = true; if (extensionMenuComponent.precedeWithSeparator()) { if (PopupMenuUtils.insertSeparatorIfNeeded(this, i)) { i++; } } } menuComponent.setVisible(enable); if (enable && extensionMenuComponent.succeedWithSeparator()) { if (PopupMenuUtils.insertSeparatorIfNeeded(this, i + 1)) { i++; } } } } PopupMenuUtils.removeTopAndBottomSeparators(this); return childEnable; } /** * Defaults to call the method {@code isEnableForComponent(Component)} passing as parameter the component returned by the * method {@code MessageContainer#getComponent()} called on the given {@code invoker}. * * @see #isEnableForComponent(Component) * @see MessageContainer#getComponent() * @since 2.3.0 */ @Override public boolean isEnableForMessageContainer(MessageContainer<?> invoker) { return isEnableForComponent(invoker.getComponent()); } public String getParentMenuName() { return null; } @Override public int getMenuIndex() { return -1; } public int getParentMenuIndex() { return -1; } public boolean isSubMenu() { return false; } @Override public boolean precedeWithSeparator() { return false; } @Override public boolean succeedWithSeparator() { return false; } @Override public boolean isSafe() { return true; } /** * Defaults to call the method {@link ExtensionPopupMenuComponent#dismissed(ExtensionPopupMenuComponent) * dismissed(ExtensionPopupMenuComponent)} on all child {@code ExtensionPopupMenuComponent}s. * * @since 2.4.0 */ @Override public void dismissed(ExtensionPopupMenuComponent selectedMenuComponent) { for (int i = 0; i < getMenuComponentCount(); ++i) { Component menuComponent = getMenuComponent(i); if (PopupMenuUtils.isExtensionPopupMenuComponent(menuComponent)) { ((ExtensionPopupMenuComponent) menuComponent).dismissed(selectedMenuComponent); } } } /** * @deprecated (2.3.0) The preparations should be made when the methods {@code isEnableForComponent(Component)} and/or * {@code isEnableForMessageContainer(MessageContainer)} are called. It will be removed in a future release. * @see #isEnableForComponent(Component) * @see #isEnableForMessageContainer(MessageContainer) */ @Deprecated public void prepareShow() { return; } }