| /* |
| * 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.myfaces.tobago.model; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import javax.swing.tree.DefaultMutableTreeNode; |
| import javax.swing.tree.TreeNode; |
| import java.lang.invoke.MethodHandles; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Stack; |
| |
| /** |
| * Implementation for a {@link TreeNode} that represents the data model for a tree. |
| */ |
| public class TreeNodeDataModel extends TreeDataModel { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| |
| private TreeNode data; |
| private int rowIndex = -1; |
| private Map<Integer, Data> mapping; |
| private Map<TreeNode, Integer> back; |
| private boolean showRoot; |
| private ExpandedState expandedState; |
| |
| /** |
| * @param data The tree data, which shall be wrapped. |
| * @param showRoot Is the root node visible. |
| * @param expandedState Defines which nodes are expanded, (XXX should it be so?) a value of {@code null} means all. |
| */ |
| public TreeNodeDataModel(final TreeNode data, final boolean showRoot, final ExpandedState expandedState) { |
| this.data = data; |
| this.showRoot = showRoot; |
| this.mapping = new HashMap<>(); |
| this.back = new HashMap<>(); |
| this.expandedState = expandedState; |
| reset(); |
| } |
| |
| @Override |
| public void reset() { |
| this.mapping.clear(); |
| this.back.clear(); |
| TreeNode current = data; |
| for (int counter = back.size(); current != null; counter++) { |
| |
| mapping.put(counter, new Data(current)); |
| back.put(current, counter); |
| |
| // if the node has children and is expanded, go to the children |
| if (current.getChildCount() > 0 && expandedState.isExpanded(current)) { |
| current = current.getChildAt(0); |
| } else { |
| current = getNextNodeButNoChild(current); |
| } |
| } |
| } |
| |
| @Override |
| public void update(final ExpandedState update) { |
| this.expandedState = update; |
| TreeNode current = data; |
| int counter = back.size(); |
| while (current != null) { |
| |
| if (!back.containsKey(current)) { |
| mapping.put(counter, new Data(current)); |
| back.put(current, counter); |
| counter++; |
| } |
| |
| // if the node has children and is expanded, go to the children |
| if (current.getChildCount() > 0 && expandedState.isExpanded(current)) { |
| current = current.getChildAt(0); |
| } else { |
| current = getNextNodeButNoChild(current); |
| } |
| } |
| } |
| |
| private TreeNode getNextNodeButNoChild(final TreeNode node) { |
| TreeNode next; |
| TreeNode p = node; |
| while (true) { |
| next = nextSibling(p); |
| if (next != null) { |
| break; |
| } |
| p = p.getParent(); |
| if (p == null) { |
| return null; |
| } |
| |
| } |
| return next; |
| } |
| |
| private TreeNode nextSibling(final TreeNode node) { |
| final TreeNode parent = node.getParent(); |
| if (parent == null) { |
| return null; |
| } |
| for (int i = 0; i < parent.getChildCount() - 1; i++) { |
| if (parent.getChildAt(i) == node) { // == is okay in this case |
| return parent.getChildAt(i + 1); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public int getRowCount() { |
| return mapping.size(); |
| } |
| |
| @Override |
| public TreeNode getRowData() { |
| return mapping.get(rowIndex).getNode(); |
| } |
| |
| @Override |
| public int getRowIndex() { |
| return rowIndex; |
| } |
| |
| @Override |
| public int getLevel() { |
| int count = -1; |
| for (TreeNode node = getRowData(); node != null; node = node.getParent()) { |
| count++; |
| } |
| return count; |
| } |
| |
| @Override |
| public TreePath getPath() { |
| return new TreePath(getRowData()); |
| } |
| |
| @Override |
| public int getDepth() { |
| if (data instanceof DefaultMutableTreeNode) { |
| return ((DefaultMutableTreeNode) data).getDepth(); |
| } |
| return -1; |
| } |
| |
| @Override |
| public boolean isFolder() { |
| return !getRowData().isLeaf(); |
| } |
| |
| @Override |
| public Object getWrappedData() { |
| return data; |
| } |
| |
| @Override |
| public boolean isRowAvailable() { |
| return 0 <= rowIndex && rowIndex < getRowCount(); |
| } |
| |
| @Override |
| public void setRowIndex(final int rowIndex) { |
| this.rowIndex = rowIndex; |
| } |
| |
| @Override |
| public void setWrappedData(final Object wrappedData) { |
| this.data = (TreeNode) wrappedData; |
| } |
| |
| @Override |
| public boolean isRowVisible() { |
| if (!isRowAvailable()) { |
| return false; |
| } |
| final TreeNode start = getRowData(); |
| if (start.getParent() == null) { |
| return showRoot; |
| } |
| TreeNode node = start.getParent(); |
| while (node != null && back.get(node) != null) { |
| final Data temp = mapping.get(back.get(node)); |
| if (temp.getNode().getParent() == null && !showRoot) { |
| return true; |
| } |
| if (!expandedState.isExpanded(new TreePath(node))) { |
| return false; |
| } |
| node = node.getParent(); |
| } |
| return true; |
| } |
| |
| @Override |
| public String getRowClientId() { |
| if (isRowAvailable()) { |
| return mapping.get(rowIndex).getClientId(); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public void setRowClientId(final String clientId) { |
| if (isRowAvailable()) { |
| mapping.get(rowIndex).setClientId(clientId); |
| } else { |
| LOG.warn("No row index set: clientId='" + clientId + "'"); |
| } |
| } |
| |
| @Override |
| public String getRowParentClientId() { |
| if (isRowAvailable()) { |
| final TreeNode parent = mapping.get(rowIndex).getNode().getParent(); |
| if (parent != null && back.get(parent) != null) { |
| return mapping.get(back.get(parent)).getClientId(); |
| } else { |
| return null; |
| } |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| public List<Integer> getRowIndicesOfChildren() { |
| final TreeNode node = getRowData(); |
| final int n = node.getChildCount(); |
| final List<Integer> children = new ArrayList<>(n); |
| for (int i = 0; i < n; i++) { |
| final Integer integer = back.get(node.getChildAt(i)); |
| if (integer != null) { // integer == null happens, when the node is not expanded |
| children.add(integer); // XXX is this a good way to handle that case? |
| } |
| } |
| return children; |
| } |
| |
| @Override |
| public List<Boolean> getJunctions() { |
| TreeNode node = getRowData(); |
| final List<Boolean> junctions = new Stack<>(); |
| while (node != null) { |
| junctions.add(hasNextSibling(node)); |
| node = node.getParent(); |
| } |
| Collections.reverse(junctions); |
| return junctions; |
| } |
| |
| private boolean hasNextSibling(final TreeNode node) { |
| final TreeNode parent = node.getParent(); |
| return parent != null && parent.getChildAt(parent.getChildCount() - 1) != node; |
| } |
| |
| /** |
| * Here we cache some state information of the nodes, because we can't access the UITreeNode state of the other nodes |
| * while rendering. |
| */ |
| private static class Data { |
| |
| private TreeNode node; |
| private String clientId; |
| |
| private Data(final TreeNode node) { |
| this.node = node; |
| } |
| |
| public TreeNode getNode() { |
| return node; |
| } |
| |
| public String getClientId() { |
| return clientId; |
| } |
| |
| public void setClientId(final String clientId) { |
| this.clientId = clientId; |
| } |
| } |
| } |