1 | package com.framsticks.gui.tree; |
---|
2 | |
---|
3 | import java.lang.ref.WeakReference; |
---|
4 | import java.util.Iterator; |
---|
5 | import java.util.LinkedList; |
---|
6 | import java.util.List; |
---|
7 | import java.util.concurrent.atomic.AtomicInteger; |
---|
8 | |
---|
9 | import javax.swing.ImageIcon; |
---|
10 | import javax.swing.tree.TreePath; |
---|
11 | |
---|
12 | import org.apache.log4j.Logger; |
---|
13 | |
---|
14 | import com.framsticks.core.ListChange; |
---|
15 | import com.framsticks.core.Node; |
---|
16 | import com.framsticks.core.Path; |
---|
17 | import com.framsticks.core.Tree; |
---|
18 | import com.framsticks.gui.Frame; |
---|
19 | import com.framsticks.gui.ImageProvider; |
---|
20 | import com.framsticks.gui.TreeAtFrame; |
---|
21 | import com.framsticks.params.AccessInterface; |
---|
22 | import com.framsticks.params.CompositeParam; |
---|
23 | import com.framsticks.params.EventListener; |
---|
24 | import com.framsticks.params.FramsClass; |
---|
25 | import com.framsticks.params.PrimitiveParam; |
---|
26 | import com.framsticks.params.ValueParam; |
---|
27 | import com.framsticks.params.types.EventParam; |
---|
28 | import com.framsticks.params.types.ObjectParam; |
---|
29 | import com.framsticks.params.types.StringParam; |
---|
30 | import com.framsticks.util.FramsticksException; |
---|
31 | import com.framsticks.util.dispatching.FutureHandler; |
---|
32 | import com.framsticks.util.lang.Casting; |
---|
33 | import com.framsticks.util.lang.Containers; |
---|
34 | import com.framsticks.util.lang.Pair; |
---|
35 | import com.framsticks.util.swing.TooltipConstructor; |
---|
36 | import static com.framsticks.core.TreeOperations.*; |
---|
37 | |
---|
38 | public class TreeNode extends AbstractNode { |
---|
39 | private static final Logger log = Logger.getLogger(TreeNode.class); |
---|
40 | |
---|
41 | |
---|
42 | protected static final AtomicInteger counter = new AtomicInteger(); |
---|
43 | |
---|
44 | protected final WeakReference<Object> reference; |
---|
45 | protected final CompositeParam param; |
---|
46 | protected final TreeAtFrame treeAtFrame; |
---|
47 | protected final List<Pair<WeakReference<Object>, WeakReference<TreeNode>>> children = new LinkedList<>(); |
---|
48 | protected final int number; |
---|
49 | protected final String textualPath; |
---|
50 | protected final ImageIcon imageIcon; |
---|
51 | protected final TreeModel treeModel; |
---|
52 | |
---|
53 | protected final List<EventListener<?>> listeners = new LinkedList<>(); |
---|
54 | |
---|
55 | public TreeNode(TreeAtFrame treeAtFrame, Path path) { |
---|
56 | path.assureResolved(); |
---|
57 | this.textualPath = path.getTextual(); |
---|
58 | this.param = path.getTop().getParam(); |
---|
59 | this.treeAtFrame = treeAtFrame; |
---|
60 | this.treeModel = treeAtFrame.getFrame().getTreeModel(); |
---|
61 | this.imageIcon = ImageProvider.loadImage(TreeCellRenderer.findIconName(param)); |
---|
62 | |
---|
63 | reference = new WeakReference<Object>(path.getTop().getObject()); |
---|
64 | number = counter.getAndIncrement(); |
---|
65 | |
---|
66 | /** Iterate over all EventParams and for matching ValueParams register listeners. */ |
---|
67 | if (param instanceof ObjectParam) { |
---|
68 | AccessInterface access = bindAccess(path); |
---|
69 | FramsClass framsClass = access.getFramsClass(); |
---|
70 | for (EventParam eventParam : Containers.filterInstanceof(framsClass.getParamEntries(), EventParam.class)) { |
---|
71 | if (!eventParam.getId().endsWith("_changed")) { |
---|
72 | continue; |
---|
73 | } |
---|
74 | String valueId = eventParam.getId().substring(0, eventParam.getId().length() - 8); |
---|
75 | final ValueParam valueParam = Casting.tryCast(ValueParam.class, framsClass.getParam(valueId)); |
---|
76 | if (valueParam == null) { |
---|
77 | continue; |
---|
78 | } |
---|
79 | registerForEventParam(path, eventParam, valueParam); |
---|
80 | } |
---|
81 | } |
---|
82 | } |
---|
83 | |
---|
84 | protected <A> void tryAddListener(final Path path, final EventParam eventParam, Class<A> argumentType, final EventListener<A> listener) { |
---|
85 | treeAtFrame.getTree().addListener(path, eventParam, listener, argumentType, new FutureHandler<Void>(treeAtFrame.getFrame()) { |
---|
86 | @Override |
---|
87 | protected void result(Void result) { |
---|
88 | assert treeAtFrame.getFrame().isActive(); |
---|
89 | log.debug("registered gui listener for " + eventParam + " at " + path); |
---|
90 | listeners.add(listener); |
---|
91 | } |
---|
92 | }); |
---|
93 | } |
---|
94 | |
---|
95 | protected void registerForEventParam(Path path, EventParam eventParam, ValueParam valueParam) { |
---|
96 | /** TODO make this listener not bind hold the reference to this TreeNode, maybe hold WeakReference internally */ |
---|
97 | if (valueParam instanceof PrimitiveParam) { |
---|
98 | |
---|
99 | tryAddListener(path, eventParam, Object.class, new EventListener<Object>() { |
---|
100 | @Override |
---|
101 | public void action(Object argument) { |
---|
102 | treeModel.loadPath(assurePath(), true); |
---|
103 | } |
---|
104 | }); |
---|
105 | |
---|
106 | } else if (valueParam instanceof CompositeParam) { |
---|
107 | |
---|
108 | final CompositeParam compositeParam = (CompositeParam) valueParam; |
---|
109 | |
---|
110 | tryAddListener(path, eventParam, ListChange.class, new EventListener<ListChange>() { |
---|
111 | @Override |
---|
112 | public void action(ListChange listChange) { |
---|
113 | assert treeAtFrame.getTree().isActive(); |
---|
114 | |
---|
115 | final Path listPath = assurePath().appendParam(compositeParam).tryFindResolution().assureResolved(); |
---|
116 | log.debug("reacting to change " + listChange + " in " + listPath); |
---|
117 | |
---|
118 | if ((listChange.getAction().equals(ListChange.Action.Modify)) && (listChange.getPosition() == -1)) { |
---|
119 | // get(listPath, future); |
---|
120 | return; |
---|
121 | } |
---|
122 | final String id = listChange.getBestIdentifier(); |
---|
123 | |
---|
124 | AccessInterface access = bindAccess(listPath); |
---|
125 | switch (listChange.getAction()) { |
---|
126 | case Add: { |
---|
127 | tryGet(listPath.getTree(), Path.appendString(listPath.getTextual(), id), new FutureHandler<Path>(treeAtFrame.getFrame()) { |
---|
128 | @Override |
---|
129 | protected void result(Path result) { |
---|
130 | final Frame frame = treeAtFrame.getFrame(); |
---|
131 | assert frame.isActive(); |
---|
132 | final TreePath treePath = frame.getTreeModel().convertToTreePath(listPath); |
---|
133 | treeModel.nodeStructureChanged(treePath); |
---|
134 | frame.updatePanelIfIsLeadSelection(treePath, listPath); |
---|
135 | log.debug("added " + id + "(" + result + ") updated " + treePath); |
---|
136 | } |
---|
137 | }); |
---|
138 | break; |
---|
139 | } |
---|
140 | case Remove: { |
---|
141 | access.set(id, null); |
---|
142 | Frame frame = treeAtFrame.getFrame(); |
---|
143 | treeModel.nodeStructureChanged(frame.getTreeModel().convertToTreePath(listPath)); |
---|
144 | break; |
---|
145 | } |
---|
146 | case Modify: { |
---|
147 | tryGet(listPath.getTree(), Path.appendString(listPath.getTextual(), id), new FutureHandler<Path>(treeAtFrame.getFrame()) { |
---|
148 | @Override |
---|
149 | protected void result(Path result) { |
---|
150 | final Frame frame = treeAtFrame.getFrame(); |
---|
151 | assert frame.isActive(); |
---|
152 | final TreePath treePath = frame.getTreeModel().convertToTreePath(result); |
---|
153 | treeModel.nodeStructureChanged(treePath); |
---|
154 | frame.updatePanelIfIsLeadSelection(treePath, listPath); |
---|
155 | } |
---|
156 | }); |
---|
157 | break; |
---|
158 | } |
---|
159 | } |
---|
160 | } |
---|
161 | }); |
---|
162 | } |
---|
163 | |
---|
164 | } |
---|
165 | |
---|
166 | protected Path assurePath() { |
---|
167 | return Path.to(treeAtFrame.getTree(), textualPath).assureResolved(); |
---|
168 | } |
---|
169 | |
---|
170 | public Node tryCreateNode() { |
---|
171 | Object child = lock(); |
---|
172 | if (child == null) { |
---|
173 | return null; |
---|
174 | } |
---|
175 | return Path.to(treeAtFrame.getTree(), textualPath).assureResolved().getTop(); |
---|
176 | } |
---|
177 | |
---|
178 | @Override |
---|
179 | public int getChildCount() { |
---|
180 | Object referent = lock(); |
---|
181 | if (referent == null) { |
---|
182 | return 0; |
---|
183 | } |
---|
184 | AccessInterface access = bindAccessFor(referent); |
---|
185 | final int count = access.getCompositeParamCount(); |
---|
186 | return count; |
---|
187 | } |
---|
188 | |
---|
189 | public TreeNode getTreeNodeForChild(Object child) { |
---|
190 | Iterator<Pair<WeakReference<Object>, WeakReference<TreeNode>>> i = children.iterator(); |
---|
191 | while (i.hasNext()) { |
---|
192 | Pair<WeakReference<Object>, WeakReference<TreeNode>> p = i.next(); |
---|
193 | Object object = p.first.get(); |
---|
194 | if (object == null) { |
---|
195 | i.remove(); |
---|
196 | continue; |
---|
197 | } |
---|
198 | TreeNode treeNode = p.second.get(); |
---|
199 | if (treeNode == null) { |
---|
200 | i.remove(); |
---|
201 | continue; |
---|
202 | } |
---|
203 | if (object == child) { |
---|
204 | return treeNode; |
---|
205 | } |
---|
206 | } |
---|
207 | return null; |
---|
208 | |
---|
209 | // WeakReference<GuiTreeNode> resultReference = children.get(child); |
---|
210 | // if (resultReference == null) { |
---|
211 | // return null; |
---|
212 | // } |
---|
213 | // return resultReference.get(); |
---|
214 | } |
---|
215 | |
---|
216 | @Override |
---|
217 | public AbstractNode getChild(int number) { |
---|
218 | Object referent = lock(); |
---|
219 | if (referent == null) { |
---|
220 | throw new FramsticksException().msg("invalid state - missing referent"); |
---|
221 | } |
---|
222 | AccessInterface access = bindAccessFor(referent); |
---|
223 | |
---|
224 | final int count = access.getCompositeParamCount(); |
---|
225 | if (number >= count) { |
---|
226 | throw new FramsticksException().msg("invalid state - no child"); |
---|
227 | } |
---|
228 | |
---|
229 | CompositeParam childParam = access.getCompositeParam(number); |
---|
230 | Object child = access.get(childParam, Object.class); |
---|
231 | if (child == null) { |
---|
232 | log.debug("returning dummy node for " + childParam + " in " + referent); |
---|
233 | return new EmptyNode(childParam); |
---|
234 | } |
---|
235 | |
---|
236 | TreeNode result = getTreeNodeForChild(child); |
---|
237 | if (result != null) { |
---|
238 | return result; |
---|
239 | } |
---|
240 | Path path = Path.to(treeAtFrame.getTree(), Path.appendString(textualPath, childParam.getId())).assureResolved(); |
---|
241 | result = new TreeNode(treeAtFrame, path); |
---|
242 | |
---|
243 | children.add(Pair.make(new WeakReference<Object>(child), new WeakReference<TreeNode>(result))); |
---|
244 | |
---|
245 | return result; |
---|
246 | |
---|
247 | } |
---|
248 | |
---|
249 | public Object lock() { |
---|
250 | return reference.get(); |
---|
251 | } |
---|
252 | |
---|
253 | @Override |
---|
254 | public int getIndexOfChild(AbstractNode child) { |
---|
255 | final TreeNode treeChild = Casting.tryCast(TreeNode.class, child); |
---|
256 | if (treeChild == null) { |
---|
257 | return -1; |
---|
258 | } |
---|
259 | final Object childObject = treeChild.lock(); |
---|
260 | final Object parentObject = lock(); |
---|
261 | if (childObject == null || parentObject == null) { |
---|
262 | return -1; |
---|
263 | } |
---|
264 | final AccessInterface access = bindAccessFor(parentObject); |
---|
265 | |
---|
266 | final int count = access.getCompositeParamCount(); |
---|
267 | for (int i = 0; i < count; ++i) { |
---|
268 | Object c = access.get(access.getCompositeParam(i), Object.class); |
---|
269 | if (c == childObject) { |
---|
270 | return i; |
---|
271 | } |
---|
272 | } |
---|
273 | log.debug(child + " not found in " + this); |
---|
274 | return -1; |
---|
275 | } |
---|
276 | |
---|
277 | /** |
---|
278 | * @return the param |
---|
279 | */ |
---|
280 | public CompositeParam getParam() { |
---|
281 | return param; |
---|
282 | } |
---|
283 | |
---|
284 | /** |
---|
285 | * @return the tree |
---|
286 | */ |
---|
287 | public Tree getTree() { |
---|
288 | return treeAtFrame.getTree(); |
---|
289 | } |
---|
290 | |
---|
291 | |
---|
292 | @Override |
---|
293 | public String toString() { |
---|
294 | return param.toString(); |
---|
295 | } |
---|
296 | |
---|
297 | public static Node tryGetNode(TreePath treePath) { |
---|
298 | return Casting.throwCast(TreeNode.class, treePath.getLastPathComponent()).tryCreateNode(); |
---|
299 | } |
---|
300 | |
---|
301 | @Override |
---|
302 | public boolean isLeaf() { |
---|
303 | Object referent = lock(); |
---|
304 | if (referent == null) { |
---|
305 | return true; |
---|
306 | } |
---|
307 | return bindAccessFor(referent).getCompositeParamCount() == 0; |
---|
308 | } |
---|
309 | |
---|
310 | protected AccessInterface bindAccessFor(Object child) { |
---|
311 | return treeAtFrame.getTree().prepareAccess(param).select(child); |
---|
312 | } |
---|
313 | |
---|
314 | @Override |
---|
315 | public void render(TreeCellRenderer renderer) { |
---|
316 | String name = param.getId(); |
---|
317 | |
---|
318 | Object child = lock(); |
---|
319 | if (child != null) { |
---|
320 | AccessInterface access = bindAccessFor(child); |
---|
321 | |
---|
322 | StringParam nameParam = Casting.tryCast(StringParam.class, access.getParam("name")); |
---|
323 | |
---|
324 | if (nameParam != null) { |
---|
325 | name = access.get(nameParam, String.class); |
---|
326 | } |
---|
327 | |
---|
328 | renderer.setToolTipText(new TooltipConstructor() |
---|
329 | .append("frams", access.getId()) |
---|
330 | .append("java", child.getClass().getCanonicalName()) |
---|
331 | .append("access", access.getClass().getSimpleName()) |
---|
332 | .append("name", name) |
---|
333 | .append("id", param.getId()) |
---|
334 | .append("object", Integer.toHexString(System.identityHashCode(child))) |
---|
335 | .append("number", number) |
---|
336 | .append("textual path", textualPath) |
---|
337 | .build()); |
---|
338 | } else { |
---|
339 | renderer.setToolTipText(new TooltipConstructor() |
---|
340 | .append("param", param) |
---|
341 | .append("textual path", textualPath) |
---|
342 | .build()); |
---|
343 | } |
---|
344 | renderer.setText(name); |
---|
345 | renderer.setIcon(imageIcon); |
---|
346 | |
---|
347 | } |
---|
348 | |
---|
349 | } |
---|