blob: ebc699aaf1818213c92a1258ad4ff69e65b89333 [file] [log] [blame]
/*
* Copyright 2000-2009 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.
*/
/*
* @author max
*/
package com.intellij.psi.impl.source.tree;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.LogUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.ILazyParseableElementType;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.text.ImmutableCharSequence;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
public class LazyParseableElement extends CompositeElement {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.source.tree.LazyParseableElement");
private static class ChameleonLock {
private ChameleonLock() {}
@NonNls
@Override
public String toString() {
return "chameleon parsing lock";
}
}
// Lock which protects expanding chameleon for this node.
// Under no circumstances should you grab the PSI_LOCK while holding this lock.
private final ChameleonLock lock = new ChameleonLock();
/** guarded by {@link #lock} */
private CharSequence myText;
private static final ThreadLocal<Boolean> ourSuppressEagerPsiCreation = new ThreadLocal<Boolean>();
public LazyParseableElement(@NotNull IElementType type, @Nullable CharSequence text) {
super(type);
if (text != null) {
synchronized (lock) {
myText = ImmutableCharSequence.asImmutable(text);
}
setCachedLength(text.length());
}
}
@Override
public void clearCaches() {
super.clearCaches();
synchronized (lock) {
if (myText != null) {
setCachedLength(myText.length());
}
}
}
@NotNull
@Override
public String getText() {
CharSequence text = myText();
if (text != null) {
return text.toString();
}
return super.getText();
}
@Override
@NotNull
public CharSequence getChars() {
CharSequence text = myText();
if (text != null) {
return text;
}
return super.getText();
}
@Override
public int getTextLength() {
CharSequence text = myText();
if (text != null) {
return text.length();
}
return super.getTextLength();
}
@Override
public int getNotCachedLength() {
CharSequence text = myText();
if (text != null) {
return text.length();
}
return super.getNotCachedLength();
}
@Override
public int hc() {
CharSequence text = myText();
return text == null ? super.hc() : LeafElement.leafHC(text);
}
@Override
protected int textMatches(@NotNull CharSequence buffer, int start) {
CharSequence text = myText();
if (text != null) {
return LeafElement.leafTextMatches(text, buffer, start);
}
return super.textMatches(buffer, start);
}
public boolean isParsed() {
return myText() == null;
}
private CharSequence myText() {
synchronized (lock) {
return myText;
}
}
@Override
final void setFirstChildNode(TreeElement child) {
if (myText() != null) {
LOG.error("Mutating collapsed chameleon");
}
super.setFirstChildNode(child);
}
@Override
final void setLastChildNode(TreeElement child) {
if (myText() != null) {
LOG.error("Mutating collapsed chameleon");
}
super.setLastChildNode(child);
}
private void ensureParsed() {
if (!ourParsingAllowed) {
LOG.error("Parsing not allowed!!!");
}
CharSequence text = myText();
if (text == null) return;
if (TreeUtil.getFileElement(this) == null) {
LOG.error("Chameleons must not be parsed till they're in file tree: " + this);
}
ApplicationManager.getApplication().assertReadAccessAllowed();
DebugUtil.startPsiModification("lazy-parsing");
try {
ILazyParseableElementType type = (ILazyParseableElementType)getElementType();
ASTNode parsedNode = type.parseContents(this);
if (parsedNode == null && text.length() > 0) {
CharSequence diagText = ApplicationManager.getApplication().isInternal() ? text : "";
LOG.error("No parse for a non-empty string: " + diagText + "; type=" + LogUtil.objectAndClass(type));
}
synchronized (lock) {
if (myText == null) return;
if (rawFirstChild() != null) {
LOG.error("Reentrant parsing?");
}
myText = null;
if (parsedNode == null) return;
super.rawAddChildrenWithoutNotifications((TreeElement)parsedNode);
}
}
finally {
DebugUtil.finishPsiModification();
}
if (!Boolean.TRUE.equals(ourSuppressEagerPsiCreation.get())) {
// create PSI all at once, to reduce contention of PsiLock in CompositeElement.getPsi()
// create PSI outside the 'lock' since this method grabs PSI_LOCK and deadlock is possible when someone else locks in the other order.
createAllChildrenPsiIfNecessary();
}
}
@Override
public void rawAddChildrenWithoutNotifications(@NotNull TreeElement first) {
if (myText() != null) {
LOG.error("Mutating collapsed chameleon");
}
super.rawAddChildrenWithoutNotifications(first);
}
@Override
public TreeElement getFirstChildNode() {
ensureParsed();
return super.getFirstChildNode();
}
@Override
public TreeElement getLastChildNode() {
ensureParsed();
return super.getLastChildNode();
}
public int copyTo(@Nullable char[] buffer, int start) {
CharSequence text = myText();
if (text == null) return -1;
if (buffer != null) {
CharArrayUtil.getChars(text, buffer, start);
}
return start + text.length();
}
private static boolean ourParsingAllowed = true;
@TestOnly
public static void setParsingAllowed(boolean allowed) {
ourParsingAllowed = allowed;
}
public static void setSuppressEagerPsiCreation(boolean suppress) {
ourSuppressEagerPsiCreation.set(suppress);
}
}