make dispose thread-safe to avoid unexpected leaks during register/dispose interleavings
[idea/community.git] / platform / util / src / com / intellij / openapi / util / ObjectNode.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.openapi.util;
3
4 import com.intellij.openapi.Disposable;
5 import com.intellij.openapi.util.objectTree.ThrowableInterner;
6 import com.intellij.util.SmartList;
7 import org.jetbrains.annotations.NonNls;
8 import org.jetbrains.annotations.NotNull;
9 import org.jetbrains.annotations.Nullable;
10 import org.jetbrains.annotations.TestOnly;
11
12 import java.util.List;
13
14 final class ObjectNode {
15   private final ObjectTree myTree;
16
17   ObjectNode myParent; // guarded by myTree.treeLock
18   private final Disposable myObject;
19
20   private List<ObjectNode> myChildren; // guarded by myTree.treeLock
21   private Throwable myTrace;
22
23   ObjectNode(@NotNull ObjectTree tree,
24              @Nullable ObjectNode parentNode,
25              @NotNull Disposable object) {
26     myTree = tree;
27     myParent = parentNode;
28     myObject = object;
29
30     myTrace = parentNode == null && Disposer.isDebugMode() ? ThrowableInterner.intern(new Throwable()) : null;
31   }
32
33   void addChild(@NotNull ObjectNode child) {
34     List<ObjectNode> children = myChildren;
35     if (children == null) {
36       myChildren = new SmartList<>(child);
37     }
38     else {
39       children.add(child);
40     }
41     child.myParent = this;
42   }
43
44   void removeChild(@NotNull ObjectNode child) {
45     List<ObjectNode> children = myChildren;
46     if (children != null) {
47       // optimisation: iterate backwards
48       for (int i = children.size() - 1; i >= 0; i--) {
49         ObjectNode node = children.get(i);
50         if (node.equals(child)) {
51           children.remove(i);
52           break;
53         }
54       }
55     }
56     child.myParent = null;
57   }
58
59   ObjectNode getParent() {
60     synchronized (myTree.treeLock) {
61       return myParent;
62     }
63   }
64
65   void getAndRemoveRecursively(@NotNull List<? super Disposable> result) {
66     if (myChildren != null) {
67       for (int i = myChildren.size() - 1; i >= 0; i--) {
68         ObjectNode childNode = myChildren.get(i);
69         childNode.getAndRemoveRecursively(result);
70       }
71     }
72     myTree.removeObjectFromTree(this);
73     // already disposed. may happen when someone does `register(obj, ()->Disposer.dispose(t));` abomination
74     if (myTree.rememberDisposedTrace(myObject) == null) {
75       result.add(myObject);
76     }
77     myChildren = null;
78     myParent = null;
79   }
80   void getAndRemoveChildrenRecursively(@NotNull List<? super Disposable> result) {
81     if (myChildren != null) {
82       for (int i = myChildren.size() - 1; i >= 0; i--) {
83         ObjectNode childNode = myChildren.get(i);
84         childNode.getAndRemoveRecursively(result);
85       }
86     }
87   }
88
89   @NotNull
90   Disposable getObject() {
91     return myObject;
92   }
93
94   @Override
95   @NonNls
96   public String toString() {
97     return "Node: " + myObject;
98   }
99
100   Throwable getTrace() {
101     return myTrace;
102   }
103
104   void clearTrace() {
105     myTrace = null;
106   }
107
108   @TestOnly
109   void assertNoReferencesKept(@NotNull Disposable aDisposable) {
110     assert getObject() != aDisposable;
111     if (myChildren != null) {
112       for (ObjectNode node: myChildren) {
113         node.assertNoReferencesKept(aDisposable);
114       }
115     }
116   }
117
118   <D extends Disposable> D findChildEqualTo(@NotNull D object) {
119     List<ObjectNode> children = myChildren;
120     if (children != null) {
121       for (ObjectNode node : children) {
122         Disposable nodeObject = node.getObject();
123         if (nodeObject.equals(object)) {
124           //noinspection unchecked
125           return (D)nodeObject;
126         }
127       }
128     }
129     return null;
130   }
131 }