de.metas.ui.web.menu.MenuTree.java Source code

Java tutorial

Introduction

Here is the source code for de.metas.ui.web.menu.MenuTree.java

Source

package de.metas.ui.web.menu;

import java.text.Normalizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

import org.compiere.util.Util;
import org.compiere.util.Util.ArrayKey;
import org.slf4j.Logger;

import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;

import de.metas.logging.LogManager;
import de.metas.printing.esb.base.util.Check;
import de.metas.ui.web.menu.MenuNode.MenuNodeFilter.MenuNodeFilterResolution;
import de.metas.ui.web.menu.MenuNode.MenuNodeType;
import de.metas.ui.web.menu.exception.NoMenuNodesFoundException;
import de.metas.ui.web.window.datatypes.DocumentId;
import de.metas.ui.web.window.datatypes.WindowId;
import lombok.NonNull;

/*
 * #%L
 * metasfresh-webui-api
 * %%
 * Copyright (C) 2016 metas GmbH
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

public final class MenuTree {
    public static final MenuTree of(final long version, final MenuNode rootNode) {
        return new MenuTree(version, rootNode);
    }

    private static final Logger logger = LogManager.getLogger(MenuTree.class);

    private final long version;

    private final MenuNode rootNode;
    private final Map<String, MenuNode> nodesById;

    private final ListMultimap<ArrayKey, MenuNode> nodesByTypeAndElementId;
    private final ListMultimap<String, MenuNode> nodesByMainTableName;

    private MenuTree(final long version, final MenuNode rootNode) {
        super();
        this.version = version;
        Preconditions.checkNotNull(rootNode, "rootNode");
        this.rootNode = rootNode;

        final ImmutableMap.Builder<String, MenuNode> nodesByIdBuilder = ImmutableMap.builder();
        final ImmutableListMultimap.Builder<ArrayKey, MenuNode> nodesByTypeAndElementIdBuilder = ImmutableListMultimap
                .builder();
        final ImmutableListMultimap.Builder<String, MenuNode> nodesByMainTableNameBuilder = ImmutableListMultimap
                .builder();
        rootNode.iterate(node -> {
            nodesByIdBuilder.put(node.getId(), node);
            nodesByTypeAndElementIdBuilder.put(mkTypeAndElementIdKey(node.getType(), node.getElementId()), node);

            final String mainTableName = node.getMainTableName();
            if (mainTableName != null) {
                nodesByMainTableNameBuilder.put(mainTableName, node);
            }
        });
        nodesById = nodesByIdBuilder.build();
        nodesByTypeAndElementId = nodesByTypeAndElementIdBuilder.build();
        nodesByMainTableName = nodesByMainTableNameBuilder.build();
    }

    private static final ArrayKey mkTypeAndElementIdKey(final MenuNodeType type, final DocumentId elementId) {
        return Util.mkKey(type, elementId);
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this).add("rootNode", rootNode).add("version", version).toString();
    }

    public long getVersion() {
        return version;
    }

    public MenuNode getRootNode() {
        return rootNode;
    }

    public MenuNode getNodeById(final String nodeId) {
        final MenuNode node = nodesById.get(nodeId);
        if (node == null) {
            throw new NoMenuNodesFoundException("No menu node found for nodeId=" + nodeId);
        }
        return node;
    }

    public Stream<MenuNode> streamNodesByAD_Menu_ID(final int adMenuId) {
        return nodesById.values().stream().filter(node -> node.getAD_Menu_ID() == adMenuId);
    }

    private MenuNode getFirstNodeByElementIdOrNull(final MenuNodeType type, final DocumentId elementId) {
        final ArrayKey key = mkTypeAndElementIdKey(type, elementId);
        final List<MenuNode> nodes = nodesByTypeAndElementId.get(key);
        if (nodes == null || nodes.isEmpty()) {
            return null;
        }

        return nodes.get(0);
    }

    public MenuNode getFirstNodeByElementId(final MenuNodeType type, final DocumentId elementId) {
        final MenuNode node = getFirstNodeByElementIdOrNull(type, elementId);
        if (node == null) {
            throw new NoMenuNodesFoundException(
                    "No menu node found for type=" + type + " and elementId=" + elementId);
        }
        return node;
    }

    public Optional<MenuNode> getNewRecordNodeForWindowId(final WindowId windowId) {
        final DocumentId elementId = windowId.toDocumentId();
        final ArrayKey key = mkTypeAndElementIdKey(MenuNodeType.NewRecord, elementId);
        final List<MenuNode> nodes = nodesByTypeAndElementId.get(key);
        if (nodes == null || nodes.isEmpty()) {
            return Optional.empty();
        }

        final MenuNode newRecordNode = nodes.get(0);
        return Optional.of(newRecordNode);
    }

    public Optional<MenuNode> getNewRecordNodeForTableName(final String tableName) {
        return nodesByMainTableName.get(tableName).stream().filter(node -> node.getType() == MenuNodeType.NewRecord)
                .findFirst();
    }

    public List<MenuNode> getPath(final String nodeId) {
        final MenuNode node = getNodeById(nodeId);
        return getPath(node);
    }

    public Optional<List<MenuNode>> getPath(final MenuNodeType type, final DocumentId elementId) {
        final MenuNode node = getFirstNodeByElementIdOrNull(type, elementId);
        if (node == null) {
            return Optional.empty();
        }

        final List<MenuNode> path = getPath(node);
        return Optional.of(path);
    }

    public MenuNode getTopLevelMenuGroupOrNull(final WindowId windowId) {
        final DocumentId elementId = windowId.toDocumentId();
        final MenuNode node = getFirstNodeByElementIdOrNull(MenuNodeType.Window, elementId);
        if (node == null) {
            return null;
        }
        final List<MenuNode> path = getPath(node);
        // NOTE: the top level menu group is at index "1" because on index "0" we have the menu root node.
        if (path.size() < 2) {
            return null;
        }
        return path.get(1);
    }

    private List<MenuNode> getPath(final MenuNode node) {
        Preconditions.checkNotNull(node, "node not null");
        final List<MenuNode> path = new ArrayList<>();

        MenuNode n = node;
        while (n != null) {
            path.add(0, n);
            n = n.getParent();
        }

        return path;
    }

    /**
     * Filters this node and its children recursively.
     *
     * @param nameQuery
     * @param includeLeafsIfGroupAccepted
     *            <ul>
     *            <li><code>false</code> populate groups only with the leafs that match (default)
     *            <li><code>true</code> if groups that were matched shall be populated with it's leafs, even if those leafs are not matching
     *            </ul>
     * @return a new copy with all matching nodes.
     */
    public MenuNode filter(final String nameQuery, final boolean includeLeafsIfGroupAccepted) {
        if (Check.isEmpty(nameQuery, true)) {
            throw new IllegalArgumentException("Invalid name query '" + nameQuery + "'");
        }

        final String nameQueryLC = nameQuery.toLowerCase();
        logger.trace("Filtering using nameQueryLC={}", nameQueryLC);

        return getRootNode().deepCopy(node -> {
            if (node.isRoot()) {
                logger.trace("Filter: accept root node: {}", node);
                return MenuNodeFilterResolution.Accept;
            }

            final boolean matches = matchesNameQuery(node, nameQueryLC);
            if (matches) {
                logger.trace("Filter: accept node because matches: {}", node);
                return MenuNodeFilterResolution.Accept;
            }

            if (node.isGroupingNode()) {
                logger.trace(
                        "Filter: accept node (if has children!) because does matches and it's a groupping node: {}",
                        node);
                return MenuNodeFilterResolution.AcceptIfHasChildren;
            } else if (includeLeafsIfGroupAccepted) {
                logger.trace(
                        "Filter: accept node if parent is accepted because does not match and we were asked to populate matching groups with its leafs: {}",
                        node);
                return MenuNodeFilterResolution.AcceptIfParentIsAccepted;
            } else {
                logger.trace("Filter: reject node because does not match and it's leaf node: {}", node);
                return MenuNodeFilterResolution.Reject;
            }
        });
    }

    private static final boolean matchesNameQuery(final MenuNode node, final String nameQueryLC) {
        final String captionNorm = stripDiacritics(node.getCaption().toLowerCase());
        final String queryNorm = stripDiacritics(nameQueryLC);
        return captionNorm.indexOf(queryNorm) >= 0;
    }

    private static final String stripDiacritics(final String string) {
        String s = Normalizer.normalize(string, Normalizer.Form.NFD);
        s = s.replaceAll("[\\p{InCombiningDiacriticalMarks}]", "");
        return s;
    }

    public MenuNode getRootNodeWithFavoritesOnly(@NonNull final MenuNodeFavoriteProvider menuNodeFavoriteProvider) {
        return getRootNode().deepCopy(node -> {
            if (node.isRoot()) {
                return MenuNodeFilterResolution.Accept;
            }

            if (node.isGroupingNode()) {
                return MenuNodeFilterResolution.AcceptIfHasChildren;
            }

            if (menuNodeFavoriteProvider.isFavorite(node)) {
                return MenuNodeFilterResolution.Accept;
            }

            return MenuNodeFilterResolution.Reject;
        });
    }
}