com.intellij.ide.hierarchy.HierarchyBrowserBaseEx.java Source code

Java tutorial

Introduction

Here is the source code for com.intellij.ide.hierarchy.HierarchyBrowserBaseEx.java

Source

/*
 * Copyright 2000-2013 JetBrains s.r.o.
 *
 * 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 com.intellij.ide.hierarchy;

import com.intellij.icons.AllIcons;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.OccurenceNavigator;
import com.intellij.ide.OccurenceNavigatorSupport;
import com.intellij.ide.PsiCopyPasteManager;
import com.intellij.ide.dnd.*;
import com.intellij.ide.dnd.aware.DnDAwareTree;
import com.intellij.ide.projectView.impl.ProjectViewTree;
import com.intellij.ide.util.scopeChooser.EditScopesDialog;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ComboBoxAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.psi.*;
import com.intellij.psi.search.scope.packageSet.NamedScope;
import com.intellij.psi.search.scope.packageSet.NamedScopesHolder;
import com.intellij.ui.ScreenUtil;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.Alarm;
import com.intellij.util.EditSourceOnDoubleClickHandler;
import com.intellij.util.NullableFunction;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.mustbe.consulo.RequiredDispatchThread;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import java.awt.*;
import java.io.File;
import java.text.MessageFormat;
import java.util.*;
import java.util.List;

public abstract class HierarchyBrowserBaseEx extends HierarchyBrowserBase implements OccurenceNavigator {
    private static final Logger LOG = Logger.getInstance("#com.intellij.ide.hierarchy.HierarchyBrowserBaseEx");

    @NonNls
    private static final String HELP_ID = "reference.toolWindows.hierarchy";

    protected final Hashtable<String, HierarchyTreeBuilder> myBuilders = new Hashtable<String, HierarchyTreeBuilder>();
    protected final Hashtable<String, JTree> myType2TreeMap = new Hashtable<String, JTree>();

    private final RefreshAction myRefreshAction = new RefreshAction();
    private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
    private SmartPsiElementPointer mySmartPsiElementPointer;
    private final CardLayout myCardLayout;
    private final JPanel myTreePanel;
    protected String myCurrentViewType;

    private boolean myCachedIsValidBase = false;

    private final HashMap<String, OccurenceNavigator> myOccurrenceNavigators = new HashMap<String, OccurenceNavigator>();

    private static final OccurenceNavigator EMPTY_NAVIGATOR = new OccurenceNavigator() {
        @Override
        public boolean hasNextOccurence() {
            return false;
        }

        @Override
        public boolean hasPreviousOccurence() {
            return false;
        }

        @Override
        public OccurenceInfo goNextOccurence() {
            return null;
        }

        @Override
        public OccurenceInfo goPreviousOccurence() {
            return null;
        }

        @Override
        public String getNextOccurenceActionName() {
            return "";
        }

        @Override
        public String getPreviousOccurenceActionName() {
            return "";
        }
    };
    public static final String SCOPE_PROJECT = IdeBundle.message("hierarchy.scope.project");
    public static final String SCOPE_ALL = IdeBundle.message("hierarchy.scope.all");
    public static final String SCOPE_TEST = IdeBundle.message("hierarchy.scope.test");
    public static final String SCOPE_CLASS = IdeBundle.message("hierarchy.scope.this.class");
    protected final Map<String, String> myType2ScopeMap = new HashMap<String, String>();

    public HierarchyBrowserBaseEx(@NotNull Project project, @NotNull PsiElement element) {
        super(project);

        setHierarchyBase(element);

        myCardLayout = new CardLayout();
        myTreePanel = new JPanel(myCardLayout);

        createTrees(myType2TreeMap);

        final HierarchyBrowserManager.State state = HierarchyBrowserManager.getInstance(project).getState();
        for (String type : myType2TreeMap.keySet()) {
            myType2ScopeMap.put(type, state.SCOPE != null ? state.SCOPE : SCOPE_ALL);
        }

        final Enumeration<String> keys = myType2TreeMap.keys();
        while (keys.hasMoreElements()) {
            final String key = keys.nextElement();
            final JTree tree = myType2TreeMap.get(key);
            myOccurrenceNavigators.put(key, new OccurenceNavigatorSupport(tree) {
                @Override
                @Nullable
                protected Navigatable createDescriptorForNode(DefaultMutableTreeNode node) {
                    final HierarchyNodeDescriptor descriptor = getDescriptor(node);
                    if (descriptor == null)
                        return null;
                    PsiElement psiElement = getOpenFileElementFromDescriptor(descriptor);
                    if (psiElement == null || !psiElement.isValid())
                        return null;
                    final VirtualFile virtualFile = psiElement.getContainingFile().getVirtualFile();
                    if (virtualFile == null)
                        return null;
                    return new OpenFileDescriptor(psiElement.getProject(), virtualFile, psiElement.getTextOffset());
                }

                @Override
                public String getNextOccurenceActionName() {
                    return getNextOccurenceActionNameImpl();
                }

                @Override
                public String getPreviousOccurenceActionName() {
                    return getPrevOccurenceActionNameImpl();
                }
            });
            myTreePanel.add(ScrollPaneFactory.createScrollPane(tree), key);
        }

        final JPanel legendPanel = createLegendPanel();
        final JPanel contentPanel;
        if (legendPanel != null) {
            contentPanel = new JPanel(new BorderLayout());
            contentPanel.add(myTreePanel, BorderLayout.CENTER);
            contentPanel.add(legendPanel, BorderLayout.SOUTH);
        } else {
            contentPanel = myTreePanel;
        }
        buildUi(createToolbar(getActionPlace(), HELP_ID).getComponent(), contentPanel);
    }

    @Nullable
    protected PsiElement getOpenFileElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor) {
        return getElementFromDescriptor(descriptor);
    }

    @Override
    @Nullable
    protected abstract PsiElement getElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor);

    @NotNull
    protected abstract String getPrevOccurenceActionNameImpl();

    @NotNull
    protected abstract String getNextOccurenceActionNameImpl();

    protected abstract void createTrees(@NotNull Map<String, JTree> trees);

    @Nullable
    protected abstract JPanel createLegendPanel();

    protected abstract boolean isApplicableElement(@NotNull PsiElement element);

    @Nullable
    protected abstract HierarchyTreeStructure createHierarchyTreeStructure(@NotNull String type,
            @NotNull PsiElement psiElement);

    @Nullable
    protected abstract Comparator<NodeDescriptor> getComparator();

    @NotNull
    protected abstract String getActionPlace();

    @NotNull
    protected abstract String getBrowserDataKey();

    protected final JTree createTree(boolean dndAware) {
        final Tree tree;
        final NullableFunction<Object, PsiElement> toPsiConverter = new NullableFunction<Object, PsiElement>() {
            @Override
            public PsiElement fun(Object o) {
                if (o instanceof HierarchyNodeDescriptor) {
                    return ((HierarchyNodeDescriptor) o).getContainingFile();
                }
                return null;
            }
        };

        if (dndAware) {
            tree = new DnDAwareTree(new DefaultTreeModel(new DefaultMutableTreeNode(""))) {
                @Override
                public void removeNotify() {
                    super.removeNotify();
                    if (ScreenUtil.isStandardAddRemoveNotify(this))
                        myRefreshAction.unregisterCustomShortcutSet(this);
                }

                @Override
                public boolean isFileColorsEnabled() {
                    return ProjectViewTree.isFileColorsEnabledFor(this);
                }

                @Override
                public Color getFileColorFor(Object object) {
                    return ProjectViewTree.getColorForObject(object, myProject, toPsiConverter);
                }
            };

            if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
                DnDManager.getInstance().registerSource(new DnDSource() {
                    @Override
                    public boolean canStartDragging(final DnDAction action, final Point dragOrigin) {
                        return getSelectedElements().length > 0;
                    }

                    @Override
                    public DnDDragStartBean startDragging(final DnDAction action, final Point dragOrigin) {
                        return new DnDDragStartBean(new TransferableWrapper() {
                            @Override
                            public TreeNode[] getTreeNodes() {
                                return tree.getSelectedNodes(TreeNode.class, null);
                            }

                            @Override
                            public PsiElement[] getPsiElements() {
                                return getSelectedElements();
                            }

                            @Override
                            public List<File> asFileList() {
                                return PsiCopyPasteManager.asFileList(getPsiElements());
                            }
                        });
                    }

                    @Override
                    public Pair<Image, Point> createDraggedImage(final DnDAction action, final Point dragOrigin) {
                        return null;
                    }

                    @Override
                    public void dragDropEnd() {
                    }

                    @Override
                    public void dropActionChanged(final int gestureModifiers) {
                    }
                }, tree);
            }
        } else {
            tree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode(""))) {
                @Override
                public void removeNotify() {
                    super.removeNotify();
                    if (ScreenUtil.isStandardAddRemoveNotify(this))
                        myRefreshAction.unregisterCustomShortcutSet(this);
                }

                @Override
                public boolean isFileColorsEnabled() {
                    return ProjectViewTree.isFileColorsEnabledFor(this);
                }

                @Override
                public Color getFileColorFor(Object object) {
                    return ProjectViewTree.getColorForObject(object, myProject, toPsiConverter);
                }
            };
        }
        configureTree(tree);
        EditSourceOnDoubleClickHandler.install(tree);
        myRefreshAction.registerShortcutOn(tree);

        return tree;
    }

    protected void setHierarchyBase(@NotNull PsiElement element) {
        mySmartPsiElementPointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(element);
    }

    private void restoreCursor() {
        myAlarm.cancelAllRequests();
        setCursor(Cursor.getDefaultCursor());
    }

    private void setWaitCursor() {
        myAlarm.addRequest(new Runnable() {
            @Override
            public void run() {
                setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
            }
        }, 100);
    }

    public final void changeView(@NotNull final String typeName) {
        myCurrentViewType = typeName;

        final PsiElement element = mySmartPsiElementPointer.getElement();
        if (element == null || !isApplicableElement(element)) {
            return;
        }

        if (myContent != null) {
            final String displayName = getContentDisplayName(typeName, element);
            if (displayName != null) {
                myContent.setDisplayName(displayName);
            }
        }

        myCardLayout.show(myTreePanel, typeName);

        if (!myBuilders.containsKey(typeName)) {
            try {
                setWaitCursor();
                // create builder
                final JTree tree = myType2TreeMap.get(typeName);
                final DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode(""));
                tree.setModel(model);

                PsiDocumentManager.getInstance(myProject).commitAllDocuments();
                final HierarchyTreeStructure structure = createHierarchyTreeStructure(typeName, element);
                if (structure == null) {
                    return;
                }
                final Comparator<NodeDescriptor> comparator = getComparator();
                final HierarchyTreeBuilder builder = new HierarchyTreeBuilder(myProject, tree, model, structure,
                        comparator);

                myBuilders.put(typeName, builder);
                Disposer.register(this, builder);

                final HierarchyNodeDescriptor descriptor = structure.getBaseDescriptor();
                builder.select(descriptor, new Runnable() {
                    @Override
                    public void run() {
                        builder.expand(descriptor, null);
                    }
                });
            } finally {
                restoreCursor();
            }
        }

        getCurrentTree().requestFocus();
    }

    @Nullable
    protected String getContentDisplayName(@NotNull String typeName, @NotNull PsiElement element) {
        if (element instanceof PsiNamedElement) {
            return MessageFormat.format(typeName, ((PsiNamedElement) element).getName());
        }
        return null;
    }

    @Override
    protected void appendActions(@NotNull DefaultActionGroup actionGroup, String helpID) {
        prependActions(actionGroup);
        actionGroup.add(myRefreshAction);
        super.appendActions(actionGroup, helpID);
    }

    protected void prependActions(final DefaultActionGroup actionGroup) {
    }

    @Override
    public boolean hasNextOccurence() {
        return getOccurrenceNavigator().hasNextOccurence();
    }

    private OccurenceNavigator getOccurrenceNavigator() {
        if (myCurrentViewType == null) {
            return EMPTY_NAVIGATOR;
        }
        final OccurenceNavigator navigator = myOccurrenceNavigators.get(myCurrentViewType);
        return navigator != null ? navigator : EMPTY_NAVIGATOR;
    }

    @Override
    public boolean hasPreviousOccurence() {
        return getOccurrenceNavigator().hasPreviousOccurence();
    }

    @Override
    public OccurenceInfo goNextOccurence() {
        return getOccurrenceNavigator().goNextOccurence();
    }

    @Override
    public OccurenceInfo goPreviousOccurence() {
        return getOccurrenceNavigator().goPreviousOccurence();
    }

    @Override
    public String getNextOccurenceActionName() {
        return getOccurrenceNavigator().getNextOccurenceActionName();
    }

    @Override
    public String getPreviousOccurenceActionName() {
        return getOccurrenceNavigator().getPreviousOccurenceActionName();
    }

    @Override
    protected HierarchyTreeBuilder getCurrentBuilder() {
        return myBuilders.get(myCurrentViewType);
    }

    protected final boolean isValidBase() {
        if (PsiDocumentManager.getInstance(myProject).getUncommittedDocuments().length > 0) {
            return myCachedIsValidBase;
        }

        final PsiElement element = mySmartPsiElementPointer.getElement();
        myCachedIsValidBase = element != null && isApplicableElement(element) && element.isValid();
        return myCachedIsValidBase;
    }

    @Override
    protected JTree getCurrentTree() {
        if (myCurrentViewType == null)
            return null;
        return myType2TreeMap.get(myCurrentViewType);
    }

    public String getCurrentViewType() {
        return myCurrentViewType;
    }

    @Override
    public Object getData(final String dataId) {
        if (getBrowserDataKey().equals(dataId)) {
            return this;
        }
        if (PlatformDataKeys.HELP_ID.is(dataId)) {
            return HELP_ID;
        }
        return super.getData(dataId);
    }

    private void disposeBuilders() {
        final Collection<HierarchyTreeBuilder> builders = myBuilders.values();
        for (final HierarchyTreeBuilder builder : builders) {
            Disposer.dispose(builder);
        }
        myBuilders.clear();
    }

    protected void doRefresh(boolean currentBuilderOnly) {
        if (currentBuilderOnly)
            LOG.assertTrue(myCurrentViewType != null);

        if (!isValidBase())
            return;

        if (getCurrentBuilder() == null)
            return; // seems like we are in the middle of refresh already

        final Ref<Pair<List<Object>, List<Object>>> storedInfo = new Ref<Pair<List<Object>, List<Object>>>();
        if (myCurrentViewType != null) {
            final HierarchyTreeBuilder builder = getCurrentBuilder();
            storedInfo.set(builder.storeExpandedAndSelectedInfo());
        }

        final PsiElement element = mySmartPsiElementPointer.getElement();
        if (element == null || !isApplicableElement(element)) {
            return;
        }
        final String currentViewType = myCurrentViewType;

        if (currentBuilderOnly) {
            Disposer.dispose(getCurrentBuilder());
            myBuilders.remove(myCurrentViewType);
        } else {
            disposeBuilders();
        }
        setHierarchyBase(element);
        validate();
        ApplicationManager.getApplication().invokeLater(new Runnable() {
            @Override
            public void run() {
                changeView(currentViewType);
                final HierarchyTreeBuilder builder = getCurrentBuilder();
                builder.restoreExpandedAndSelectedInfo(storedInfo.get());
            }
        });
    }

    protected String getCurrentScopeType() {
        if (myCurrentViewType == null)
            return null;
        return myType2ScopeMap.get(myCurrentViewType);
    }

    protected class AlphaSortAction extends ToggleAction {
        public AlphaSortAction() {
            super(IdeBundle.message("action.sort.alphabetically"), IdeBundle.message("action.sort.alphabetically"),
                    AllIcons.ObjectBrowser.Sorted);
        }

        @Override
        public final boolean isSelected(final AnActionEvent event) {
            return HierarchyBrowserManager.getInstance(myProject).getState().SORT_ALPHABETICALLY;
        }

        @Override
        public final void setSelected(final AnActionEvent event, final boolean flag) {
            final HierarchyBrowserManager hierarchyBrowserManager = HierarchyBrowserManager.getInstance(myProject);
            hierarchyBrowserManager.getState().SORT_ALPHABETICALLY = flag;
            final Comparator<NodeDescriptor> comparator = getComparator();
            final Collection<HierarchyTreeBuilder> builders = myBuilders.values();
            for (final HierarchyTreeBuilder builder : builders) {
                builder.setNodeDescriptorComparator(comparator);
            }
        }

        @Override
        public final void update(final AnActionEvent event) {
            super.update(event);
            final Presentation presentation = event.getPresentation();
            presentation.setEnabled(isValidBase());
        }
    }

    protected static class BaseOnThisElementAction extends AnAction {
        private final String myActionId;
        private final String myBrowserDataKey;

        public BaseOnThisElementAction(String text, String actionId, String browserDataKey) {
            super(text);
            myActionId = actionId;
            myBrowserDataKey = browserDataKey;
        }

        @Override
        public final void actionPerformed(final AnActionEvent event) {
            final DataContext dataContext = event.getDataContext();
            final HierarchyBrowserBaseEx browser = (HierarchyBrowserBaseEx) dataContext.getData(myBrowserDataKey);
            if (browser == null)
                return;

            final PsiElement selectedElement = browser.getSelectedElement();
            if (selectedElement == null || !browser.isApplicableElement(selectedElement))
                return;

            final String currentViewType = browser.myCurrentViewType;
            Disposer.dispose(browser);
            browser.setHierarchyBase(selectedElement);
            browser.validate();
            ApplicationManager.getApplication().invokeLater(new Runnable() {
                @Override
                public void run() {
                    browser.changeView(correctViewType(browser, currentViewType));
                }
            });
        }

        protected String correctViewType(HierarchyBrowserBaseEx browser, String viewType) {
            return viewType;
        }

        @Override
        public final void update(final AnActionEvent event) {
            final Presentation presentation = event.getPresentation();

            registerCustomShortcutSet(ActionManager.getInstance().getAction(myActionId).getShortcutSet(), null);

            final DataContext dataContext = event.getDataContext();
            final HierarchyBrowserBaseEx browser = (HierarchyBrowserBaseEx) dataContext.getData(myBrowserDataKey);
            if (browser == null) {
                presentation.setVisible(false);
                presentation.setEnabled(false);
                return;
            }

            presentation.setVisible(true);

            final PsiElement selectedElement = browser.getSelectedElement();
            if (selectedElement == null || !browser.isApplicableElement(selectedElement)) {
                presentation.setEnabled(false);
                presentation.setVisible(false);
                return;
            }

            presentation.setEnabled(isEnabled(browser, selectedElement));
            String nonDefaultText = getNonDefaultText(browser, selectedElement);
            if (nonDefaultText != null) {
                presentation.setText(nonDefaultText);
            }
        }

        protected boolean isEnabled(@NotNull HierarchyBrowserBaseEx browser, @NotNull PsiElement element) {
            return !element.equals(browser.mySmartPsiElementPointer.getElement()) && element.isValid();
        }

        @Nullable
        protected String getNonDefaultText(@NotNull HierarchyBrowserBaseEx browser, @NotNull PsiElement element) {
            return null;
        }
    }

    private class RefreshAction extends com.intellij.ide.actions.RefreshAction {
        public RefreshAction() {
            super(IdeBundle.message("action.refresh"), IdeBundle.message("action.refresh"),
                    AllIcons.Actions.Refresh);
        }

        @Override
        public final void actionPerformed(final AnActionEvent e) {
            doRefresh(false);
        }

        @Override
        public final void update(final AnActionEvent event) {
            final Presentation presentation = event.getPresentation();
            presentation.setEnabled(isValidBase());
        }
    }

    public class ChangeScopeAction extends ComboBoxAction {
        @RequiredDispatchThread
        @Override
        public final void update(final AnActionEvent e) {
            final Presentation presentation = e.getPresentation();
            final Project project = CommonDataKeys.PROJECT.getData(e.getDataContext());
            if (project == null)
                return;
            presentation.setEnabled(isEnabled());
            presentation.setText(getCurrentScopeType());
        }

        protected boolean isEnabled() {
            return true;
        }

        @Override
        @NotNull
        protected final DefaultActionGroup createPopupActionGroup(final JComponent button) {
            final DefaultActionGroup group = new DefaultActionGroup();

            for (String name : getValidScopeNames()) {
                group.add(new MenuAction(name));
            }

            group.add(new ConfigureScopesAction());

            return group;
        }

        private Collection<String> getValidScopeNames() {
            List<String> result = new ArrayList<String>();
            result.add(SCOPE_PROJECT);
            result.add(SCOPE_TEST);
            result.add(SCOPE_ALL);
            result.add(SCOPE_CLASS);

            final NamedScopesHolder[] holders = NamedScopesHolder.getAllNamedScopeHolders(myProject);
            for (NamedScopesHolder holder : holders) {
                NamedScope[] scopes = holder.getEditableScopes(); //predefined scopes already included
                for (NamedScope scope : scopes) {
                    result.add(scope.getName());
                }
            }
            return result;
        }

        private void selectScope(final String scopeType) {
            myType2ScopeMap.put(myCurrentViewType, scopeType);
            HierarchyBrowserManager.getInstance(myProject).getState().SCOPE = scopeType;

            // invokeLater is called to update state of button before long tree building operation
            ApplicationManager.getApplication().invokeLater(new Runnable() {
                @Override
                public void run() {
                    doRefresh(true); // scope is kept per type so other builders doesn't need to be refreshed
                }
            });
        }

        @Override
        public final JComponent createCustomComponent(final Presentation presentation) {
            final JPanel panel = new JPanel(new GridBagLayout());
            panel.add(new JLabel(IdeBundle.message("label.scope")), new GridBagConstraints(0, 0, 1, 1, 0, 0,
                    GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 5, 0, 0), 0, 0));
            panel.add(super.createCustomComponent(presentation), new GridBagConstraints(1, 0, 1, 1, 1, 1,
                    GridBagConstraints.WEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
            return panel;
        }

        private final class MenuAction extends AnAction {
            private final String myScopeType;

            public MenuAction(final String scopeType) {
                super(scopeType);
                myScopeType = scopeType;
            }

            @Override
            public final void actionPerformed(final AnActionEvent e) {
                selectScope(myScopeType);
            }
        }

        private final class ConfigureScopesAction extends AnAction {
            private ConfigureScopesAction() {
                super("Configure...");
            }

            @Override
            public void actionPerformed(AnActionEvent e) {
                EditScopesDialog.showDialog(myProject, null);
                if (!getValidScopeNames().contains(myType2ScopeMap.get(myCurrentViewType))) {
                    selectScope(SCOPE_ALL);
                }
            }
        }
    }
}