source: java/main/src/main/java/com/framsticks/gui/tree/TreeModel.java @ 100

Last change on this file since 100 was 100, checked in by psniegowski, 11 years ago

HIGHLIGHTS:

  • add <include/> to configuration
  • add side notes to tree
    • used to store arbitrary information alongside the tree structure
  • migrate to log4j2
    • supports lazy string evaluation of passed arguments
  • improve GUI tree
    • it stays in synchronization with actual state (even in high load test scenario)
  • improve panel management in GUI
  • make loading objects in GUI more lazy
  • offload parsing to connection receiver thread
    • info parsing
    • first step of objects parsing
  • fix connection parsing bug (eof in long values)
  • support zero-arguments procedure in table view

CHANGELOG:
Implement procedure calls from table view.

Refactorization around procedures in tables.

Add table editor for buttons.

Render buttons in the the list view.

Further improve Columns.

Add Column class for TableModel?.

Accept also non-arguments ProcedureParams? in tableView.

Increase maximal TextAreaControl? size.

Add tooltip to ProcedureControl?.

Fix bug of interpreting eofs in long values by connection reader.

Further rework connection parsing.

Simplify client connection processing.

Test ListChange? modification.

Test ListChange? events with java server.

Add TestChild?.

Fix bug with fast deregistering when connecting to running server.

Another minor refactorization in TreeOperations?.

Fix bug in SimpleAbstractAccess? loading routine.

Another minor improvement.

Minor change.

Make reading of List objects two-phase.

Another minor change.

Dispatch parsing into receiver thread.

Another step.

Enclose passing value in ObjectParam? case in closure.

Minor step.

Minor change on way to offload parsing.

Temporarily comment out single ValueParam? get.

It will be generalized to multi ValueParam?.

Process info in receiver thread.

Add DispatchingExceptionHandler?.

Make waits in browser test longer.

Use FETCHED_MARK.

It is honored in GUI, where it used to decide whether to get values

after user action.

It is set in standard algorithm for processing fetched values.

Add remove operation to side notes.

Make loading more lazy.

Improve loading policy.

On node choose load itself, on node expansion, load children.

Minor improvement.

Fix bug with panel interleaving.

Minor improvements.

Improve panel management.

More cleaning around panels.

Reorganize panels.

Further improve tree.

Fix bug in TreeModel?.

Remove children from TreeNode?.

Implement TreeNode? hashCode and equals.

Make TreeNode? delegate equals and hashcode to internal reference.

Move listeners from TreeNode? to side notes.

Store path.textual as a side note.

Side note params instead of accesses for objects.

More refactorizations.

In TreeNode? bindAccess based on side notes.

Minor step.

Hide createAccess.

Rename AccessInterface? to Access.

Minor changes.

Several improvements in high load scenarios.

Change semantics of ArrayListAccess?.set(index, null);

It now removes the element, making list shorter
(it was set to null before).

Add path remove handler.

Handle exceptions in Connection.

Update .gitignore

Configure logging to file.

Move registration to TreeModel?.

Further refactorization.

Minor refactorization.

Minor improvements.

Use specialized event also for Modify action of ListChange?.

Use remove events.

Use the insertion events for tree.

Further improve tree refreshing.

Further improve reacting on events in GUI.

Fix problem with not adding objects on addition list change.

Migrate to log4j lazy String construction interface.

Migrate imports to log4j2.

Drop dependency on adapter to version 1.2.

Switch log4j implementation to log4j2.

Add dirty mark to the NodeAtFrame?.

Make selecting in AccessInterfaces? type safe.

Ignore containers size settings in Model and Genotype.

Use tree side notes to remember local changes and panels.

Add sideNotes to tree.

They will be used to store various accompanying information
right in the tree.

Use ReferenceIdentityMap? from apache in TreeNode?.

It suits the need perfectly (weak semantics on both key and value).

Make ArrayListParam? do not react size changes.

Guard in TableModel? before not yet loaded objects.

Add <include/> clause and AutoInjector?.

Extract common columns configuration to separate xml,
that can be included by other configurations.

File size: 12.1 KB
Line 
1package com.framsticks.gui.tree;
2
3import java.util.Enumeration;
4import java.util.Iterator;
5import java.util.LinkedList;
6import java.util.List;
7
8import javax.annotation.Nullable;
9import javax.swing.event.TreeModelEvent;
10import javax.swing.event.TreeModelListener;
11import javax.swing.tree.TreePath;
12
13import org.apache.logging.log4j.Logger;
14import org.apache.logging.log4j.LogManager;
15
16import com.framsticks.core.ListChange;
17import com.framsticks.core.Node;
18import com.framsticks.core.Path;
19import com.framsticks.core.TreeOperations;
20import com.framsticks.gui.Frame;
21import com.framsticks.params.Access;
22import com.framsticks.params.CompositeParam;
23import com.framsticks.params.EventListener;
24import com.framsticks.params.ListAccess;
25import com.framsticks.params.PrimitiveParam;
26import com.framsticks.params.Util;
27import com.framsticks.params.ValueParam;
28import com.framsticks.params.types.EventParam;
29import com.framsticks.util.FramsticksException;
30import com.framsticks.util.Misc;
31import com.framsticks.util.UnsupportedOperationException;
32import com.framsticks.util.dispatching.FutureHandler;
33import com.framsticks.util.lang.Casting;
34
35import static com.framsticks.core.TreeOperations.*;
36
37public class TreeModel implements javax.swing.tree.TreeModel {
38        private static final Logger log = LogManager.getLogger(TreeModel.class);
39
40
41        protected List<TreeModelListener> listeners = new LinkedList<>();
42
43
44        protected final Frame frame;
45
46        /**
47         * @param frame
48         */
49        public TreeModel(Frame frame) {
50                this.frame = frame;
51        }
52
53        @Override
54        public void addTreeModelListener(TreeModelListener listener) {
55                listeners.add(listener);
56        }
57
58        @Override
59        public Object getChild(Object parent, int number) {
60                return Casting.throwCast(AbstractNode.class, parent).getChild(number);
61        }
62
63        @Override
64        public int getChildCount(Object parent) {
65                return Casting.throwCast(AbstractNode.class, parent).getChildCount();
66        }
67
68        @Override
69        public int getIndexOfChild(Object parent, Object child) {
70                if ((parent == null) || (child == null)) {
71                        return -1;
72                }
73                return Casting.throwCast(AbstractNode.class, parent).getIndexOfChild(child);
74        }
75
76        @Override
77        public MetaNode getRoot() {
78                return frame.getRootNode();
79        }
80
81        @Override
82        public boolean isLeaf(Object node) {
83                return Casting.throwCast(AbstractNode.class, node).isLeaf();
84        }
85
86        @Override
87        public void removeTreeModelListener(TreeModelListener listener) {
88                listeners.remove(listener);
89        }
90
91        @Override
92        public void valueForPathChanged(TreePath path, Object value) {
93                throw new UnsupportedOperationException().msg("changing value of tree node");
94        }
95
96
97        protected boolean changing = false;
98
99        public void treeNodesInserted(TreeModelEvent event) {
100                assert frame.isActive();
101                try {
102                        for (TreeModelListener listener : listeners) {
103                                listener.treeNodesInserted(event);
104                        }
105                } catch (ArrayIndexOutOfBoundsException e) {
106                }
107        }
108
109        public void treeNodesRemoved(TreeModelEvent event) {
110                assert frame.isActive();
111                try {
112                        for (TreeModelListener listener : listeners) {
113                                listener.treeNodesRemoved(event);
114                        }
115                } catch (ArrayIndexOutOfBoundsException e) {
116                }
117        }
118
119        public void treeNodesChanged(TreeModelEvent event) {
120                try {
121                        for (TreeModelListener listener : listeners) {
122                                listener.treeNodesChanged(event);
123                        }
124                } catch (ArrayIndexOutOfBoundsException e) {
125                }
126        }
127
128        public TreeModelEvent prepareModelEvent(TreePath treePath, int number, TreeNode node) {
129                return new TreeModelEvent(this, treePath, new int[] {number}, new Object[] { node });
130        }
131
132
133        public TreeModelEvent prepareModelEventRegarding(Access access, String id, TreePath treeListPath) {
134
135                int number = Util.getNumberOfCompositeParamChild(access, access.get(id, Object.class));
136                if (number == -1) {
137                        log.debug("encountered minor tree inconsistency in {}", treeListPath);
138                        return null;
139                }
140                TreeNode node = Casting.throwCast(TreeNode.class, Casting.throwCast(TreeNode.class, treeListPath.getLastPathComponent()).getChild(number));
141                return prepareModelEvent(treeListPath, number, node);
142        }
143
144        public void treeStructureChanged(TreePath treePath) {
145
146                if (treePath == null) {
147                        return;
148                }
149                assert frame.isActive();
150
151                changing = true;
152                log.debug("changing structure: {}", treePath);
153                Enumeration<TreePath> expanded = frame.getJtree().getExpandedDescendants(treePath);
154                TreePath selection = frame.getJtree().getSelectionPath();
155
156                try {
157                        for (TreeModelListener listener : listeners) {
158                                listener.treeStructureChanged(new TreeModelEvent(this, treePath));
159                        }
160                } catch (ArrayIndexOutOfBoundsException e) {
161                }
162
163
164                if (expanded != null) {
165                        while (expanded.hasMoreElements()) {
166                                TreePath expansion = expanded.nextElement();
167                                // log.info("reexpanding: {}", expansion);
168                                frame.getJtree().expandPath(expansion);
169                        }
170                }
171
172                if (selection != null) {
173                        frame.getJtree().setSelectionPath(selection);
174                }
175                changing = false;
176        }
177
178        /**
179         *
180         * This method may return null on conversion failure, which may happen in highload situations.
181         */
182        public @Nullable Path convertToPath(TreePath treePath) {
183                final Object[] components = treePath.getPath();
184                assert components[0] == frame.getRootNode();
185                if (components.length == 1) {
186                        return null;
187                }
188                Path.PathBuilder builder = Path.build();
189                builder.tree(Casting.assertCast(TreeNode.class, components[1]).getTree());
190                List<Node> nodes = new LinkedList<>();
191                for (int i = 1; i < components.length; ++i) {
192                        TreeNode treeNode = Casting.tryCast(TreeNode.class, components[i]);
193                        if (treeNode == null) {
194                                return null;
195                        }
196                        Node node = treeNode.tryCreateNode();
197                        if (node == null) {
198                                return null;
199                                // throw new FramsticksException().msg("failed to recreate path").arg("treePath", treePath);
200                        }
201                        nodes.add(node);
202                }
203                builder.buildUpTo(nodes, null);
204
205                return builder.finish();
206        }
207
208        public TreePath convertToTreePath(Path path, boolean forceComplete) {
209                assert frame.isActive();
210
211                List<Object> accumulator = new LinkedList<Object>();
212                accumulator.add(getRoot());
213
214                for (Object r : getRoot().getChildren()) {
215                        if (r instanceof TreeNode) {
216                                TreeNode root = (TreeNode) r;
217                                if (root.getTree() == path.getTree()) {
218                                        Iterator<Node> n = path.getNodes().iterator();
219                                        TreeNode treeNode = root;
220                                        accumulator.add(root);
221                                        n.next();
222                                        while (n.hasNext()) {
223                                                Node node = n.next();
224                                                treeNode = treeNode.prepareTreeNodeForChild(Path.build().tree(path.getTree()).buildUpTo(path.getNodes(), node).finish());
225                                                if (treeNode == null) {
226                                                        break;
227                                                }
228                                                accumulator.add(treeNode);
229                                        }
230                                        break;
231                                }
232                        }
233                }
234                return new TreePath(accumulator.toArray());
235        }
236
237        /**
238         * @return the listeners
239         */
240        public List<TreeModelListener> getListeners() {
241                return listeners;
242        }
243
244        /**
245         * @return the changing
246         */
247        public boolean isChanging() {
248                return changing;
249        }
250
251        public void loadChildren(Path path, boolean reload) {
252                if (path == null) {
253                        return;
254                }
255                Access access = TreeOperations.bindAccess(path);
256
257                int count = access.getCompositeParamCount();
258                for (int i = 0; i < count; ++i) {
259                        Path childPath = path.appendParam(access.getCompositeParam(i)).tryFindResolution();
260                        loadPath(childPath, reload);
261                }
262        }
263
264        public void loadPath(Path path, boolean reload) {
265                if (path == null) {
266                        return;
267                }
268                if (!reload && path.isResolved() && isMarked(path.getTree(), path.getTopObject(), FETCHED_MARK, false)) {
269                        return;
270                }
271                path.getTree().get(path, new FutureHandler<Path>(frame) {
272                        @Override
273                        protected void result(Path result) {
274                                final TreePath treePath = convertToTreePath(result, true);
275
276
277                                if (treePath != null) {
278                                        treeStructureChanged(treePath);
279                                        frame.updatePanelIfIsLeadSelection(result);
280                                }
281                        }
282                });
283        }
284
285        public void expandTreeNode(TreePath treePath) {
286                assert frame.isActive();
287                if (treePath == null) {
288                        return;
289                }
290                if (isChanging()) {
291                        return;
292                }
293                Path path = convertToPath(treePath);
294                if (path == null) {
295                        return;
296                }
297                loadChildren(path.assureResolved(), false);
298        }
299
300        public void chooseTreeNode(final TreePath treePath) {
301                assert frame.isActive();
302                if (treePath == null) {
303                        return;
304                }
305                if (isChanging()) {
306                        return;
307                }
308
309                Path path = convertToPath(treePath);
310                if (path == null) {
311                        return;
312                }
313                path = path.assureResolved();
314
315                log.debug("choosing {}", path);
316                frame.showPanelForTreePath(treePath);
317                loadPath(path, false);
318
319        }
320
321
322        protected void registerForEventParam(final TreeNode treeNode, Path path, final EventParam eventParam, ValueParam valueParam) {
323                /** TODO make this listener not bind hold the reference to this TreeNode, maybe hold WeakReference internally */
324                if (valueParam instanceof PrimitiveParam) {
325
326                        treeNode.tryAddListener(path, eventParam, Object.class, new EventListener<Object>() {
327                                @Override
328                                public void action(Object argument) {
329                                        loadPath(treeNode.assurePath(), true);
330                                }
331                        });
332
333                } else if (valueParam instanceof CompositeParam) {
334
335                        final CompositeParam compositeParam = (CompositeParam) valueParam;
336
337                        treeNode.tryAddListener(path, eventParam, ListChange.class, new EventListener<ListChange>() {
338                                @Override
339                                public void action(ListChange listChange) {
340                                        assert treeNode.getTree().isActive();
341
342                                        Path parentPath = treeNode.assurePath();
343                                        final Path listPath = parentPath.appendParam(compositeParam).tryFindResolution();
344                                        if (!listPath.isResolved()) {
345                                                /** that situation is quietly ignored - it may happen if first event comes before the container was resolved */
346                                                return;
347                                        }
348
349                                        log.debug("reacting to change {} in {}", listChange, listPath);
350                                        final TreePath treeListPath = convertToTreePath(listPath, true);
351                                        if (treeListPath == null) {
352                                                throw new FramsticksException().msg("path was not fully converted").arg("path", listPath);
353                                        }
354
355                                        if ((listChange.getAction().equals(ListChange.Action.Modify)) && (listChange.getPosition() == -1)) {
356                                                // get(listPath, future);
357                                                // treeModel.nodeStructureChanged(treePath);
358                                                // frame.updatePanelIfIsLeadSelection(treePath, result);
359                                                return;
360                                        }
361                                        final String id = listChange.getBestIdentifier();
362
363                                        final ListAccess access = (ListAccess) bindAccess(listPath);
364                                        switch (listChange.getAction()) {
365                                                case Add: {
366                                                        Path childPath = listPath.appendParam(access.prepareParamFor(id)).tryFindResolution();
367                                                        if (!childPath.isResolved()) {
368                                                                childPath = create(childPath);
369
370                                                                TreeModelEvent event = prepareModelEventRegarding(access, id, treeListPath);
371                                                                if (event != null) {
372                                                                        treeNodesInserted(event);
373                                                                } else {
374                                                                        treeStructureChanged(treeListPath);
375                                                                }
376                                                                frame.updatePanelIfIsLeadSelection(listPath);
377                                                        }
378
379                                                        listPath.getTree().get(childPath, new FutureHandler<Path>(frame) {
380                                                                @Override
381                                                                protected void result(Path result) {
382                                                                        if (!result.isResolved()) {
383                                                                                log.warn("inconsistency after addition list change: {}", result);
384                                                                        }
385                                                                        assert frame.isActive();
386                                                                        final TreePath treePath = Misc.throwIfNull(frame.getTreeModel().convertToTreePath(result, true));
387
388                                                                        // treeModel.nodeStructureChanged(treePath);
389                                                                        frame.updatePanelIfIsLeadSelection(result);
390
391                                                                        log.debug("added {}({}) updated {}", id, result, treePath);
392                                                                }
393                                                        });
394                                                        break;
395                                                }
396                                                case Remove: {
397
398                                                        TreeModelEvent event = prepareModelEventRegarding(access, id, treeListPath);
399                                                        access.set(id, null);
400                                                        if (event != null) {
401                                                                treeNodesRemoved(event);
402                                                        } else {
403                                                                treeStructureChanged(treeListPath);
404                                                        }
405
406                                                        frame.updatePanelIfIsLeadSelection(listPath);
407
408                                                        break;
409                                                }
410                                                case Modify: {
411                                                        Path childPath = listPath.appendParam(access.prepareParamFor(id)).tryResolveIfNeeded();
412                                                        listPath.getTree().get(childPath, new FutureHandler<Path>(frame) {
413                                                                @Override
414                                                                protected void result(Path result) {
415                                                                        assert frame.isActive();
416                                                                        // final TreePath treePath = frame.getTreeModel().convertToTreePath(result, true);
417
418                                                                        TreeModelEvent event = prepareModelEventRegarding(access, id, treeListPath);
419                                                                        if (event != null) {
420                                                                                treeNodesChanged(event);
421                                                                        } else {
422                                                                                treeStructureChanged(treeListPath);
423                                                                        }
424
425                                                                        frame.updatePanelIfIsLeadSelection(listPath);
426                                                                        frame.updatePanelIfIsLeadSelection(result);
427                                                                }
428                                                        });
429                                                        break;
430                                                }
431                                        }
432                                }
433                        });
434                }
435
436        }
437
438
439
440        protected final Object createdTag = new Object();
441
442
443
444}
Note: See TracBrowser for help on using the repository browser.