package com.framsticks.gui.tree; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.annotation.Nullable; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreePath; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import com.framsticks.core.ListChange; import com.framsticks.core.Node; import com.framsticks.core.Path; import com.framsticks.core.TreeOperations; import com.framsticks.gui.Frame; import com.framsticks.params.Access; import com.framsticks.params.CompositeParam; import com.framsticks.params.EventListener; import com.framsticks.params.ListAccess; import com.framsticks.params.PrimitiveParam; import com.framsticks.params.Util; import com.framsticks.params.ValueParam; import com.framsticks.params.types.EventParam; import com.framsticks.util.FramsticksException; import com.framsticks.util.Misc; import com.framsticks.util.UnsupportedOperationException; import com.framsticks.util.dispatching.FutureHandler; import com.framsticks.util.lang.Casting; import static com.framsticks.core.TreeOperations.*; public class TreeModel implements javax.swing.tree.TreeModel { private static final Logger log = LogManager.getLogger(TreeModel.class); protected List listeners = new LinkedList<>(); protected final Frame frame; /** * @param frame */ public TreeModel(Frame frame) { this.frame = frame; } @Override public void addTreeModelListener(TreeModelListener listener) { listeners.add(listener); } @Override public Object getChild(Object parent, int number) { return Casting.throwCast(AbstractNode.class, parent).getChild(number); } @Override public int getChildCount(Object parent) { return Casting.throwCast(AbstractNode.class, parent).getChildCount(); } @Override public int getIndexOfChild(Object parent, Object child) { if ((parent == null) || (child == null)) { return -1; } return Casting.throwCast(AbstractNode.class, parent).getIndexOfChild(child); } @Override public MetaNode getRoot() { return frame.getRootNode(); } @Override public boolean isLeaf(Object node) { return Casting.throwCast(AbstractNode.class, node).isLeaf(); } @Override public void removeTreeModelListener(TreeModelListener listener) { listeners.remove(listener); } @Override public void valueForPathChanged(TreePath path, Object value) { throw new UnsupportedOperationException().msg("changing value of tree node"); } protected boolean changing = false; public void treeNodesInserted(TreeModelEvent event) { assert frame.isActive(); try { for (TreeModelListener listener : listeners) { listener.treeNodesInserted(event); } } catch (ArrayIndexOutOfBoundsException e) { } } public void treeNodesRemoved(TreeModelEvent event) { assert frame.isActive(); try { for (TreeModelListener listener : listeners) { listener.treeNodesRemoved(event); } } catch (ArrayIndexOutOfBoundsException e) { } } public void treeNodesChanged(TreeModelEvent event) { try { for (TreeModelListener listener : listeners) { listener.treeNodesChanged(event); } } catch (ArrayIndexOutOfBoundsException e) { } } public TreeModelEvent prepareModelEvent(TreePath treePath, int number, TreeNode node) { return new TreeModelEvent(this, treePath, new int[] {number}, new Object[] { node }); } public TreeModelEvent prepareModelEventRegarding(Access access, String id, TreePath treeListPath) { int number = Util.getNumberOfCompositeParamChild(access, access.get(id, Object.class)); if (number == -1) { log.debug("encountered minor tree inconsistency in {}", treeListPath); return null; } TreeNode node = Casting.throwCast(TreeNode.class, Casting.throwCast(TreeNode.class, treeListPath.getLastPathComponent()).getChild(number)); return prepareModelEvent(treeListPath, number, node); } public void treeStructureChanged(TreePath treePath) { if (treePath == null) { return; } assert frame.isActive(); changing = true; log.debug("changing structure: {}", treePath); Enumeration expanded = frame.getJtree().getExpandedDescendants(treePath); TreePath selection = frame.getJtree().getSelectionPath(); try { for (TreeModelListener listener : listeners) { listener.treeStructureChanged(new TreeModelEvent(this, treePath)); } } catch (ArrayIndexOutOfBoundsException e) { } if (expanded != null) { while (expanded.hasMoreElements()) { TreePath expansion = expanded.nextElement(); // log.info("reexpanding: {}", expansion); frame.getJtree().expandPath(expansion); } } if (selection != null) { frame.getJtree().setSelectionPath(selection); } changing = false; } /** * * This method may return null on conversion failure, which may happen in highload situations. */ public @Nullable Path convertToPath(TreePath treePath) { final Object[] components = treePath.getPath(); assert components[0] == frame.getRootNode(); if (components.length == 1) { return null; } Path.PathBuilder builder = Path.build(); builder.tree(Casting.assertCast(TreeNode.class, components[1]).getTree()); List nodes = new LinkedList<>(); for (int i = 1; i < components.length; ++i) { TreeNode treeNode = Casting.tryCast(TreeNode.class, components[i]); if (treeNode == null) { return null; } Node node = treeNode.tryCreateNode(); if (node == null) { return null; // throw new FramsticksException().msg("failed to recreate path").arg("treePath", treePath); } nodes.add(node); } builder.buildUpTo(nodes, null); return builder.finish(); } public TreePath convertToTreePath(Path path, boolean forceComplete) { assert frame.isActive(); List accumulator = new LinkedList(); accumulator.add(getRoot()); for (Object r : getRoot().getChildren()) { if (r instanceof TreeNode) { TreeNode root = (TreeNode) r; if (root.getTree() == path.getTree()) { Iterator n = path.getNodes().iterator(); TreeNode treeNode = root; accumulator.add(root); n.next(); while (n.hasNext()) { Node node = n.next(); treeNode = treeNode.prepareTreeNodeForChild(Path.build().tree(path.getTree()).buildUpTo(path.getNodes(), node).finish()); if (treeNode == null) { break; } accumulator.add(treeNode); } break; } } } return new TreePath(accumulator.toArray()); } /** * @return the listeners */ public List getListeners() { return listeners; } /** * @return the changing */ public boolean isChanging() { return changing; } public void loadChildren(Path path, boolean reload) { if (path == null) { return; } Access access = TreeOperations.bindAccess(path); int count = access.getCompositeParamCount(); for (int i = 0; i < count; ++i) { Path childPath = path.appendParam(access.getCompositeParam(i)).tryFindResolution(); loadPath(childPath, reload); } } public void loadPath(Path path, boolean reload) { if (path == null) { return; } if (!reload && path.isResolved() && isMarked(path.getTree(), path.getTopObject(), FETCHED_MARK, false)) { return; } path.getTree().get(path, new FutureHandler(frame) { @Override protected void result(Path result) { final TreePath treePath = convertToTreePath(result, true); if (treePath != null) { treeStructureChanged(treePath); frame.updatePanelIfIsLeadSelection(result); } } }); } public void expandTreeNode(TreePath treePath) { assert frame.isActive(); if (treePath == null) { return; } if (isChanging()) { return; } Path path = convertToPath(treePath); if (path == null) { return; } loadChildren(path.assureResolved(), false); } public void chooseTreeNode(final TreePath treePath) { assert frame.isActive(); if (treePath == null) { return; } if (isChanging()) { return; } Path path = convertToPath(treePath); if (path == null) { return; } path = path.assureResolved(); log.debug("choosing {}", path); frame.showPanelForTreePath(treePath); loadPath(path, false); } protected void registerForEventParam(final TreeNode treeNode, Path path, final EventParam eventParam, ValueParam valueParam) { /** TODO make this listener not bind hold the reference to this TreeNode, maybe hold WeakReference internally */ if (valueParam instanceof PrimitiveParam) { treeNode.tryAddListener(path, eventParam, Object.class, new EventListener() { @Override public void action(Object argument) { loadPath(treeNode.assurePath(), true); } }); } else if (valueParam instanceof CompositeParam) { final CompositeParam compositeParam = (CompositeParam) valueParam; treeNode.tryAddListener(path, eventParam, ListChange.class, new EventListener() { @Override public void action(ListChange listChange) { assert treeNode.getTree().isActive(); Path parentPath = treeNode.assurePath(); final Path listPath = parentPath.appendParam(compositeParam).tryFindResolution(); if (!listPath.isResolved()) { /** that situation is quietly ignored - it may happen if first event comes before the container was resolved */ return; } log.debug("reacting to change {} in {}", listChange, listPath); final TreePath treeListPath = convertToTreePath(listPath, true); if (treeListPath == null) { throw new FramsticksException().msg("path was not fully converted").arg("path", listPath); } if ((listChange.getAction().equals(ListChange.Action.Modify)) && (listChange.getPosition() == -1)) { // get(listPath, future); // treeModel.nodeStructureChanged(treePath); // frame.updatePanelIfIsLeadSelection(treePath, result); return; } final String id = listChange.getBestIdentifier(); final ListAccess access = (ListAccess) bindAccess(listPath); switch (listChange.getAction()) { case Add: { Path childPath = listPath.appendParam(access.prepareParamFor(id)).tryFindResolution(); if (!childPath.isResolved()) { childPath = create(childPath); TreeModelEvent event = prepareModelEventRegarding(access, id, treeListPath); if (event != null) { treeNodesInserted(event); } else { treeStructureChanged(treeListPath); } frame.updatePanelIfIsLeadSelection(listPath); } listPath.getTree().get(childPath, new FutureHandler(frame) { @Override protected void result(Path result) { if (!result.isResolved()) { log.warn("inconsistency after addition list change: {}", result); } assert frame.isActive(); final TreePath treePath = Misc.throwIfNull(frame.getTreeModel().convertToTreePath(result, true)); // treeModel.nodeStructureChanged(treePath); frame.updatePanelIfIsLeadSelection(result); log.debug("added {}({}) updated {}", id, result, treePath); } }); break; } case Remove: { TreeModelEvent event = prepareModelEventRegarding(access, id, treeListPath); access.set(id, null); if (event != null) { treeNodesRemoved(event); } else { treeStructureChanged(treeListPath); } frame.updatePanelIfIsLeadSelection(listPath); break; } case Modify: { Path childPath = listPath.appendParam(access.prepareParamFor(id)).tryResolveIfNeeded(); listPath.getTree().get(childPath, new FutureHandler(frame) { @Override protected void result(Path result) { assert frame.isActive(); // final TreePath treePath = frame.getTreeModel().convertToTreePath(result, true); TreeModelEvent event = prepareModelEventRegarding(access, id, treeListPath); if (event != null) { treeNodesChanged(event); } else { treeStructureChanged(treeListPath); } frame.updatePanelIfIsLeadSelection(listPath); frame.updatePanelIfIsLeadSelection(result); } }); break; } } } }); } } protected final Object createdTag = new Object(); }