blob: f70ca422fa3ed8764ebeca008b596e0cb28bf471 [file] [log] [blame]
/*
* 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;
}
}
}