/*
* 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.usages.impl;
import com.intellij.usages.UsageView;
import com.intellij.util.BitUtil;
import com.intellij.util.Consumer;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull;
import javax.swing.tree.DefaultMutableTreeNode;
import java.util.Vector;
/**
* @author max
*/
public abstract class Node extends DefaultMutableTreeNode {
private int myCachedTextHash;
private byte myCachedFlags; // bit packed flags below:
static final byte EXCLUDED_MASK = 1 << 3;
private static final byte UPDATED_MASK = 1 << 4;
private static final byte CACHED_INVALID_MASK = 1;
private static final byte CACHED_READ_ONLY_MASK = 1 << 1;
private static final byte READ_ONLY_COMPUTED_MASK = 1 << 2;
@MagicConstant(intValues = {CACHED_INVALID_MASK, CACHED_READ_ONLY_MASK, READ_ONLY_COMPUTED_MASK, EXCLUDED_MASK, UPDATED_MASK})
private @interface FlagConstant {
}
private boolean isFlagSet(@FlagConstant byte mask) {
return BitUtil.isSet(myCachedFlags, mask);
}
private void setFlag(@FlagConstant byte mask, boolean value) {
myCachedFlags = BitUtil.set(myCachedFlags, mask, value);
}
Node() {
}
/**
* debug method for producing string tree presentation
*/
public abstract String tree2string(int indent, String lineSeparator);
/**
* isDataXXX methods perform actual (expensive) data computation.
* Called from {@link #update(UsageView, Consumer)})
* to be compared later with cached data stored in {@link #myCachedFlags} and {@link #myCachedTextHash}
*/
protected abstract boolean isDataValid();
protected abstract boolean isDataReadOnly();
protected abstract boolean isDataExcluded();
protected abstract String getText(@NotNull UsageView view);
public final boolean isValid() {
return !isFlagSet(CACHED_INVALID_MASK);
}
public final boolean isReadOnly() {
boolean result;
boolean computed = isFlagSet(READ_ONLY_COMPUTED_MASK);
if (computed) {
result = isFlagSet(CACHED_READ_ONLY_MASK);
}
else {
result = isDataReadOnly();
setFlag(READ_ONLY_COMPUTED_MASK, true);
setFlag(CACHED_READ_ONLY_MASK, result);
}
return result;
}
public final boolean isExcluded() {
return isFlagSet(EXCLUDED_MASK);
}
final synchronized void update(@NotNull UsageView view, @NotNull Consumer<Node> edtNodeChangedQueue) {
boolean isDataValid = isDataValid();
boolean isReadOnly = isDataReadOnly();
String text = getText(view);
boolean cachedValid = isValid();
boolean cachedReadOnly = isFlagSet(CACHED_READ_ONLY_MASK);
if (isDataValid != cachedValid || isReadOnly != cachedReadOnly || myCachedTextHash != text.hashCode()) {
setFlag(CACHED_INVALID_MASK, !isDataValid);
setFlag(CACHED_READ_ONLY_MASK, isReadOnly);
myCachedTextHash = text.hashCode();
updateNotify();
edtNodeChangedQueue.consume(this);
}
setFlag(UPDATED_MASK, true);
}
void markNeedUpdate() {
setFlag(UPDATED_MASK, false);
}
boolean needsUpdate() {
return !isFlagSet(UPDATED_MASK);
}
/**
* Override to perform node-specific updates
*/
protected void updateNotify() {
}
// same as DefaultMutableTreeNode.insert() except it doesn't try to remove the newChild from its parent since we know it's new
void insertNewNode(@NotNull Node newChild, int childIndex) {
if (children == null) {
children = new Vector();
}
children.insertElementAt(newChild, childIndex);
}
void setExcluded(boolean excluded, @NotNull Consumer<Node> edtNodeChangedQueue) {
setFlag(EXCLUDED_MASK, excluded);
edtNodeChangedQueue.consume(this);
}
}