/* * Copyright 2000-2015 JetBrains s.r.o. * * Licensed 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 com.intellij.openapi.util.objectTree; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.util.Disposer; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.util.Collection; import java.util.Collections; import java.util.List; final class ObjectNode<T> { private static final ObjectNode[] EMPTY_ARRAY = new ObjectNode[0]; private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.objectTree.ObjectNode"); private final ObjectTree<T> myTree; private ObjectNode<T> myParent; // guarded by myTree.treeLock private final T myObject; private List<ObjectNode<T>> myChildren; // guarded by myTree.treeLock private final Throwable myTrace; private final long myOwnModification; ObjectNode(@NotNull ObjectTree<T> tree, @Nullable ObjectNode<T> parentNode, @NotNull T object, long modification) { myTree = tree; myParent = parentNode; myObject = object; myTrace = Disposer.isDebugMode() ? ThrowableInterner.intern(new Throwable()) : null; myOwnModification = modification; } @SuppressWarnings("unchecked") @NotNull private ObjectNode<T>[] getChildrenArray() { List<ObjectNode<T>> children = myChildren; if (children == null || children.isEmpty()) return EMPTY_ARRAY; return children.toArray(new ObjectNode[children.size()]); } void addChild(@NotNull ObjectNode<T> child) { List<ObjectNode<T>> children = myChildren; if (children == null) { myChildren = new SmartList<ObjectNode<T>>(child); } else { children.add(child); } child.myParent = this; } void removeChild(@NotNull ObjectNode<T> child) { List<ObjectNode<T>> children = myChildren; if (children != null) { // optimisation: iterate backwards for (int i = children.size() - 1; i >= 0; i--) { ObjectNode<T> node = children.get(i); if (node.equals(child)) { children.remove(i); break; } } } child.myParent = null; } ObjectNode<T> getParent() { return myParent; } @NotNull Collection<ObjectNode<T>> getChildren() { synchronized (myTree.treeLock) { if (myChildren == null) return Collections.emptyList(); return Collections.unmodifiableCollection(myChildren); } } void execute(@NotNull final ObjectTreeAction<T> action) { ObjectTree.executeActionWithRecursiveGuard(this, myTree.getNodesInExecution(), new ObjectTreeAction<ObjectNode<T>>() { @Override public void execute(@NotNull ObjectNode<T> each) { try { action.beforeTreeExecution(myObject); } catch (Throwable t) { LOG.error(t); } ObjectNode<T>[] childrenArray; synchronized (myTree.treeLock) { childrenArray = getChildrenArray(); } List<Throwable> exceptions = new SmartList<Throwable>(); for (int i = childrenArray.length - 1; i >= 0; i--) { try { childrenArray[i].execute(action); } catch (Throwable e) { exceptions.add(e); } } synchronized (myTree.treeLock) { myChildren = null; } try { action.execute(myObject); myTree.fireExecuted(myObject); } catch (Throwable e) { exceptions.add(e); } remove(); handleExceptions(exceptions); } @Override public void beforeTreeExecution(@NotNull ObjectNode<T> parent) { } }); } private static void handleExceptions(List<Throwable> exceptions) { if (!exceptions.isEmpty()) { for (Throwable exception : exceptions) { if (!(exception instanceof ProcessCanceledException)) { LOG.error(exception); } } ProcessCanceledException pce = ContainerUtil.findInstance(exceptions, ProcessCanceledException.class); if (pce != null) { throw pce; } } } private void remove() { synchronized (myTree.treeLock) { myTree.putNode(myObject, null); if (myParent == null) { myTree.removeRootObject(myObject); } else { myParent.removeChild(this); } } } @NotNull T getObject() { return myObject; } @Override @NonNls public String toString() { return "Node: " + myObject; } Throwable getTrace() { return myTrace; } @TestOnly void assertNoReferencesKept(@NotNull T aDisposable) { assert getObject() != aDisposable; synchronized (myTree.treeLock) { if (myChildren != null) { for (ObjectNode<T> node: myChildren) { node.assertNoReferencesKept(aDisposable); } } } } Throwable getAllocation() { return myTrace; } long getOwnModification() { return myOwnModification; } long getModification() { return getOwnModification(); } <D extends Disposable> D findChildEqualTo(@NotNull D object) { synchronized (myTree.treeLock) { List<ObjectNode<T>> children = myChildren; if (children != null) { for (ObjectNode<T> node : children) { T nodeObject = node.getObject(); if (nodeObject.equals(object)) { //noinspection unchecked return (D)nodeObject; } } } return null; } } }