org.opencms.ui.contextmenu.CmsContextMenuTreeBuilder Maven / Gradle / Ivy
Show all versions of opencms-test Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* For further information about Alkacon Software, please see the
* company website: http://www.alkacon.com
*
* For further information about OpenCms, please see the
* project website: http://www.opencms.org
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.opencms.ui.contextmenu;
import org.opencms.main.CmsLog;
import org.opencms.ui.I_CmsDialogContext;
import org.opencms.ui.actions.CmsContextMenuActionItem;
import org.opencms.ui.actions.I_CmsDefaultAction;
import org.opencms.util.CmsTreeNode;
import org.opencms.workplace.explorer.menu.CmsMenuItemVisibilityMode;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* Helper class for building context menus from the list of available context menu items.
*/
public class CmsContextMenuTreeBuilder {
/** The logger instance for this class. */
private static final Log LOG = CmsLog.getLog(CmsContextMenuTreeBuilder.class);
/** The dialog context. */
private I_CmsDialogContext m_context;
/** The default action item. */
private I_CmsContextMenuItem m_defaultActionItem;
/** Cached visibilities for context menu entries. */
private IdentityHashMap m_visiblities = new IdentityHashMap();
/**
* Creates a new instance.
*
* @param context the dialog context
*/
public CmsContextMenuTreeBuilder(I_CmsDialogContext context) {
m_context = context;
}
/**
* Builds the complete context menu from the given available items.
*
* @param availableItems the available items
*
* @return the complete context menu
*/
public CmsTreeNode buildAll(List availableItems) {
CmsTreeNode result = buildTree(availableItems);
removeEmptySubtrees(result);
return result;
}
/**
* Builds a tree from a list of available context menu items.
*
* The root node of the returned tree has no useful data, its child nodes correspond to the top-level
* entries of the ccontext menu.
*
* @param items the available context menu items
* @return the context menu item tree
*/
public CmsTreeNode buildTree(List items) {
items = Lists.newArrayList(items);
// First sort by priority and then use a map with the id as the key to store the items,
// eliminating items with the same id but a lower priority than another item
Collections.sort(items, new Comparator() {
public int compare(I_CmsContextMenuItem a, I_CmsContextMenuItem b) {
return Integer.compare(a.getPriority(), b.getPriority());
}
});
LinkedHashMap itemsById = Maps.newLinkedHashMap();
for (I_CmsContextMenuItem item : items) {
String id = item.getId();
I_CmsContextMenuItem prevItem = itemsById.get(id);
if (prevItem != null) {
LOG.info(
"Discarding overridden context menu item " + prevItem + " because of higher priority item " + item);
}
itemsById.put(id, item);
}
// Now sort by order. Since all children of a node should be processed in one iteration of the following loop,
// this order also applies to the child order of each tree node built in the next step
List uniqueItems = Lists.newArrayList(itemsById.values());
uniqueItems = filterVisible(uniqueItems);
if (m_context.getResources().size() == 1) {
m_defaultActionItem = findDefaultAction(uniqueItems);
}
Collections.sort(uniqueItems, new Comparator() {
public int compare(I_CmsContextMenuItem a, I_CmsContextMenuItem b) {
return Float.compare(a.getOrder(), b.getOrder());
}
});
Set processedIds = Sets.newHashSet();
boolean changed = true;
Map> treesById = Maps.newHashMap();
// Create childless tree node for each item
for (I_CmsContextMenuItem item : itemsById.values()) {
CmsTreeNode node = new CmsTreeNode();
node.setData(item);
treesById.put(item.getId(), node);
}
CmsTreeNode root = new CmsTreeNode();
// Use null as the root node, which does not have any useful data
treesById.put(null, root);
// Iterate through list multiple times, each time only processing those items whose parents
// we have encountered in a previous iteration (actually, in the last iteration). We do this so that the resulting
// tree is actually a tree and contains no cycles, even if there is a reference cycle between the context menu items via their parent ids.
// (Items which form such a cycle will never be reached.)
while (changed) {
changed = false;
Iterator iterator = uniqueItems.iterator();
Set currentLevel = Sets.newHashSet();
while (iterator.hasNext()) {
I_CmsContextMenuItem currentItem = iterator.next();
String parentId = currentItem.getParentId();
if ((parentId == null) || processedIds.contains(parentId)) {
changed = true;
iterator.remove();
currentLevel.add(currentItem.getId());
treesById.get(parentId).addChild(treesById.get(currentItem.getId()));
}
}
processedIds.addAll(currentLevel);
}
return root;
}
/**
* Filters out invisible context menu items from a given list.
*
* @param items the items
*
* @return the list of context menu items
*/
public List filterVisible(List items) {
List result = Lists.newArrayList();
for (I_CmsContextMenuItem item : items) {
CmsMenuItemVisibilityMode visibility = getVisibility(item);
if (!visibility.isInVisible()) {
result.add(item);
}
}
return result;
}
/**
* Returns the default action item if available.
* Only available once {@link #buildTree(List)} or {@link #buildAll(List)} has been executed.
*
* @return the default action item
*/
public I_CmsContextMenuItem getDefaultActionItem() {
return m_defaultActionItem;
}
/**
* Gets the visibility for a given item (cached, if possible).
*
* @param item the item
* @return the visibility of that item
*/
public CmsMenuItemVisibilityMode getVisibility(I_CmsContextMenuItem item) {
CmsMenuItemVisibilityMode result = m_visiblities.get(item);
if (result == null) {
result = item.getVisibility(m_context);
m_visiblities.put(item, result);
}
return result;
}
/**
* Recursively remove subtrees (destructively!) which do not contain any 'leaf' context menu items.
*
* @param root the root of the tree to process
*/
public void removeEmptySubtrees(CmsTreeNode root) {
List> children = root.getChildren();
if ((root.getData() != null) && root.getData().isLeafItem()) {
children.clear();
} else {
Iterator> iter = children.iterator();
while (iter.hasNext()) {
CmsTreeNode node = iter.next();
removeEmptySubtrees(node);
if ((node.getData() != null) && !node.getData().isLeafItem() && (node.getChildren().size() == 0)) {
iter.remove();
}
}
}
}
/**
* Evaluates the default action if any for highlighting within the menu.
*
* @param items the menu items
*
* @return the default action if available
*/
private I_CmsContextMenuItem findDefaultAction(Collection items) {
I_CmsContextMenuItem result = null;
int resultRank = -1;
for (I_CmsContextMenuItem menuItem : items) {
if ((menuItem instanceof CmsContextMenuActionItem)
&& (((CmsContextMenuActionItem)menuItem).getWorkplaceAction() instanceof I_CmsDefaultAction)) {
I_CmsDefaultAction action = (I_CmsDefaultAction)((CmsContextMenuActionItem)menuItem).getWorkplaceAction();
if (getVisibility(menuItem).isActive()) {
if (result == null) {
result = menuItem;
resultRank = action.getDefaultActionRank(m_context);
} else {
int rank = action.getDefaultActionRank(m_context);
if (rank > resultRank) {
result = menuItem;
resultRank = rank;
}
}
}
}
}
return result;
}
}