IDEA-56517 (no more recursion in Java stub builder)

This commit is contained in:
Roman Shevchenko
2010-11-22 16:07:38 +03:00
parent 0494b06061
commit 11b1a5ea4a
7 changed files with 157 additions and 50 deletions
@@ -28,6 +28,8 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.stubs.StubUpdatingIndex;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiUtil;
@@ -149,10 +151,19 @@ public class JavaParserUtil {
assert psi != null : chameleon;
final Project project = psi.getProject();
CharSequence text;
if (TreeUtil.isCollapsedChameleon(chameleon)) {
text = chameleon.getChars();
}
else {
text = psi.getUserData(StubUpdatingIndex.FILE_TEXT_CONTENT_KEY);
if (text == null) text = chameleon.getChars();
}
final PsiBuilderFactory factory = PsiBuilderFactory.getInstance();
final LanguageLevel level = PsiUtil.getLanguageLevel(psi);
final Lexer lexer = JavaParserDefinition.createLexer(level);
final PsiBuilder builder = factory.createBuilder(project, chameleon, lexer, psi.getLanguage(), chameleon.getChars());
final PsiBuilder builder = factory.createBuilder(project, chameleon, lexer, psi.getLanguage(), text);
setLanguageLevel(builder, level);
return builder;
@@ -49,6 +49,8 @@ import java.io.IOException;
* @author max
*/
public class JavaFieldStubElementType extends JavaStubElementType<PsiFieldStub, PsiField> {
private static final int INITIALIZER_LENGTH_LIMIT = 1000;
public JavaFieldStubElementType(@NotNull @NonNls final String id) {
super(id);
}
@@ -125,6 +127,10 @@ public class JavaFieldStubElementType extends JavaStubElementType<PsiFieldStub,
return PsiFieldStub.INITIALIZER_NOT_STORED;
}
if (initializer.getTextLength() > INITIALIZER_LENGTH_LIMIT) {
return PsiFieldStub.INITIALIZER_TOO_LONG;
}
return initializer.getText();
}
@@ -134,6 +140,10 @@ public class JavaFieldStubElementType extends JavaStubElementType<PsiFieldStub,
return PsiFieldStub.INITIALIZER_NOT_STORED;
}
if (initializer.getEndOffset() - initializer.getStartOffset() > INITIALIZER_LENGTH_LIMIT) {
return PsiFieldStub.INITIALIZER_TOO_LONG;
}
return LightTreeUtil.toFilteredString(tree, initializer, null);
}
@@ -33,8 +33,6 @@ import com.intellij.util.io.StringRef;
import org.jetbrains.annotations.NotNull;
public class PsiFieldStubImpl extends StubBase<PsiField> implements PsiFieldStub {
private static final int INITIALIZER_LENGTH_LIMIT = 1000;
private final StringRef myName;
private final TypeInfo myType;
private final StringRef myInitializer;
@@ -51,15 +49,9 @@ public class PsiFieldStubImpl extends StubBase<PsiField> implements PsiFieldStub
public PsiFieldStubImpl(final StubElement parent, final StringRef name, @NotNull TypeInfo type, final StringRef initializer, final byte flags) {
super(parent, isEnumConst(flags) ? JavaStubElementTypes.ENUM_CONSTANT : JavaStubElementTypes.FIELD);
if (initializer != null && initializer.length() > INITIALIZER_LENGTH_LIMIT) {
myInitializer = StringRef.fromString(INITIALIZER_TOO_LONG);
}
else {
myInitializer = initializer;
}
myName = name;
myType = type;
myInitializer = initializer;
myFlags = flags;
}
@@ -22,10 +22,13 @@ import com.intellij.psi.impl.source.tree.LazyParseableElement;
import com.intellij.psi.stubs.StubElement;
import com.intellij.testFramework.LightIdeaTestCase;
import java.security.SecureRandom;
public class JavaStubBuilderTest extends LightIdeaTestCase {
private static final StubBuilder OLD_BUILDER = new JavaFileStubBuilder();
private static final StubBuilder NEW_BUILDER = new JavaLightStubBuilder();
private static final int SOE_TEST_DEPTH = 10000;
@Override
public void setUp() throws Exception {
@@ -33,6 +36,12 @@ public class JavaStubBuilderTest extends LightIdeaTestCase {
doTest("@interface A { int i() default 42; }\n class C { void m(int p) throws E { } }", null); // warm up
}
public void testEmpty() {
doTest("/**/",
"PsiJavaFileStub []\n" +
" IMPORT_LIST:PsiImportListStub\n");
}
public void testFileHeader() {
doTest("package p;\n" +
"import a/*comment to skip*/.b;\n" +
@@ -256,6 +265,32 @@ public class JavaStubBuilderTest extends LightIdeaTestCase {
" THROWS_LIST:PsiRefListStub[THROWS_LIST:]\n");
}
public void testSOEProof() throws Exception {
final StringBuilder sb = new StringBuilder();
final SecureRandom random = new SecureRandom();
sb.append("class SOE_test {\n BigInteger BIG = new BigInteger(\n");
for (int i = 0; i < SOE_TEST_DEPTH; i++) {
sb.append(" \"").append(Math.abs(random.nextInt())).append("\" +\n");
}
sb.append(" \"\");\n}");
final PsiJavaFile file = (PsiJavaFile)createLightFile("SOE_test.java", sb.toString());
long t = System.currentTimeMillis();
final StubElement tree = NEW_BUILDER.buildStubTree(file);
t = System.currentTimeMillis() - t;
assertEquals("PsiJavaFileStub []\n" +
" IMPORT_LIST:PsiImportListStub\n" +
" CLASS:PsiClassStub[name=SOE_test fqn=SOE_test]\n" +
" MODIFIER_LIST:PsiModifierListStub[mask=4096]\n" +
" TYPE_PARAMETER_LIST:PsiTypeParameterListStub\n" +
" EXTENDS_LIST:PsiRefListStub[EXTENDS_LIST:]\n" +
" IMPLEMENTS_LIST:PsiRefListStub[IMPLEMENTS_LIST:]\n" +
" FIELD:PsiFieldStub[BIG:BigInteger=;INITIALIZER_NOT_STORED;]\n" +
" MODIFIER_LIST:PsiModifierListStub[mask=4096]\n",
DebugUtil.stubTreeToString(tree));
System.out.println("SOE depth=" + SOE_TEST_DEPTH + ", time=" + t + "ms");
}
private static void doTest(final String source, final String tree) {
final PsiJavaFile file = (PsiJavaFile)createLightFile("test.java", source);
final LazyParseableElement fileNode = (LazyParseableElement)file.getNode();
@@ -264,25 +299,28 @@ public class JavaStubBuilderTest extends LightIdeaTestCase {
long t1 = System.nanoTime();
final StubElement lighterTree = NEW_BUILDER.buildStubTree(file);
t1 = System.nanoTime() - t1;
t1 = (System.nanoTime() - t1)/1000;
assertFalse(fileNode.isParsed());
long t2 = System.nanoTime();
final StubElement originalTree = OLD_BUILDER.buildStubTree(file);
t2 = System.nanoTime() - t2;
t2 = (System.nanoTime() - t2)/1000;
assertTrue(fileNode.isParsed());
final String lightStr = DebugUtil.stubTreeToString(originalTree);
final String originalStr = DebugUtil.stubTreeToString(lighterTree);
final String lightStr = DebugUtil.stubTreeToString(lighterTree);
final String originalStr = DebugUtil.stubTreeToString(originalTree);
if (tree != null) {
final String msg = "light=" + t1/1000 + "mks, heavy=" + t2/1000 + "mks";
final String msg = "light=" + t1 + "mks, heavy=" + t2 + "mks, gain=" + (100 * (t2 - t1) / t2) + "%";
// todo: assertTrue("Expected to be at least 2x faster: " + msg, 2*t1 <= t2);
System.out.println(msg);
assertEquals("wrong test data", tree, lightStr);
assertEquals("wrong test data", tree, originalStr);
if (!"".equals(tree)) {
assertEquals("light tree differs", tree, originalStr);
assertEquals("light tree differs", tree, lightStr);
}
}
else {
assertEquals(originalStr, lightStr);
}
}
}
@@ -20,19 +20,18 @@ import com.intellij.openapi.module.StdModuleTypes;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.impl.JavaSdkImpl;
import com.intellij.psi.impl.JavaPsiFacadeEx;
import org.jetbrains.annotations.NonNls;
/**
* A testcase that provides IDEA application and project. Note both are reused for each test run in the session so
* A test case that provides IDEA application and project. Note both are reused for each test run in the session so
* be careful to return all the modification made to application and project components (such as settings) after
* test is finished so other test aren't affected. The project is initialized with single module that have single
* content&amp;source entry. For your convenience the project may be equipped with some mock JDK so your tests may
* content and source entry. For your convenience the project may be equipped with some mock JDK so your tests may
* refer to external classes. In order to enable this feature you have to have a folder named "mockJDK" under
* idea installation home that is used for test running. Place src.zip under that folder. We'd suggest this is real mock
* so it contains classes that is really needed in order to speed up tests startup.
*/
@SuppressWarnings({"HardCodedStringLiteral"})
@NonNls public class LightIdeaTestCase extends LightPlatformTestCase {
public abstract class LightIdeaTestCase extends LightPlatformTestCase {
@SuppressWarnings({"JUnitTestCaseWithNonTrivialConstructors"})
public LightIdeaTestCase() {
IdeaTestCase.initPlatformPrefix();
}
@@ -41,7 +40,6 @@ import org.jetbrains.annotations.NonNls;
return JavaPsiFacadeEx.getInstanceEx(ourProject);
}
@Override
protected Sdk getProjectJDK() {
return JavaSdkImpl.getMockJdk17();
@@ -23,11 +23,16 @@ import com.intellij.psi.PsiFile;
import com.intellij.psi.StubBuilder;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.ILightStubFileElementType;
import gnu.trove.TIntStack;
import java.util.List;
import java.util.Stack;
public class LightStubBuilder implements StubBuilder {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.stubs.LightStubBuilder");
@Override
public StubElement buildStubTree(final PsiFile file) {
final FileASTNode node = file.getNode();
assert node != null;
@@ -39,7 +44,9 @@ public class LightStubBuilder implements StubBuilder {
final ILightStubFileElementType<?> type = (ILightStubFileElementType)node.getElementType();
final LighterAST tree = new LighterAST(node.getCharTable(), type.parseContentsLight(node));
return buildStubTreeFor(tree, tree.getRoot(), createStubForFile(file, tree));
final StubElement rootStub = createStubForFile(file, tree);
buildStubTree(tree, tree.getRoot(), rootStub);
return rootStub;
}
protected StubElement createStubForFile(final PsiFile file, final LighterAST tree) {
@@ -47,15 +54,61 @@ public class LightStubBuilder implements StubBuilder {
return new PsiFileStubImpl(file);
}
protected StubElement buildStubTreeFor(final LighterAST tree, final LighterASTNode element, final StubElement parentStub) {
StubElement stub = parentStub;
protected void buildStubTree(final LighterAST tree, final LighterASTNode root, final StubElement rootStub) {
final Stack<LighterASTNode> parents = new Stack<LighterASTNode>();
final TIntStack childNumbers = new TIntStack();
final Stack<StubElement> parentStubs = new Stack<StubElement>();
LighterASTNode parent = null;
LighterASTNode element = root;
List<LighterASTNode> children = null;
int childNumber = 0;
StubElement parentStub = rootStub;
nextElement:
while (element != null) {
final StubElement stub = createStub(tree, element, parentStub);
final List<LighterASTNode> kids = tree.getChildren(element);
if (kids.size() > 0) {
if (parent != null) {
parents.push(parent);
childNumbers.push(childNumber);
parentStubs.push(parentStub);
}
parent = element;
element = (children = kids).get(childNumber = 0);
parentStub = stub;
if (!skipChildProcessingWhenBuildingStubs(parent.getTokenType(), element.getTokenType())) continue nextElement;
}
while (children != null && ++childNumber < children.size()) {
element = children.get(childNumber);
if (!skipChildProcessingWhenBuildingStubs(parent.getTokenType(), element.getTokenType())) continue nextElement;
}
element = null;
while (parents.size() > 0) {
children = tree.getChildren((parent = parents.pop()));
childNumber = childNumbers.pop();
parentStub = parentStubs.pop();
while (++childNumber < children.size()) {
element = children.get(childNumber);
if (!skipChildProcessingWhenBuildingStubs(parent.getTokenType(), element.getTokenType())) continue nextElement;
}
element = null;
}
}
}
@SuppressWarnings({"MethodMayBeStatic"})
protected StubElement createStub(final LighterAST tree, final LighterASTNode element, final StubElement parentStub) {
final IElementType elementType = element.getTokenType();
if (elementType instanceof IStubElementType) {
if (elementType instanceof ILightStubElementType) {
final ILightStubElementType lightElementType = (ILightStubElementType)elementType;
if (lightElementType.shouldCreateStub(tree, element, parentStub)) {
stub = lightElementType.createStub(tree, element, parentStub);
return lightElementType.createStub(tree, element, parentStub);
}
}
else {
@@ -63,16 +116,11 @@ public class LightStubBuilder implements StubBuilder {
}
}
for (final LighterASTNode child : tree.getChildren(element)) {
if (!skipChildProcessingWhenBuildingStubs(elementType, child.getTokenType())) {
buildStubTreeFor(tree, child, stub);
}
}
return stub;
return parentStub;
}
public boolean skipChildProcessingWhenBuildingStubs(IElementType nodeType, IElementType childType) {
@Override
public boolean skipChildProcessingWhenBuildingStubs(final IElementType nodeType, final IElementType childType) {
return false;
}
}
@@ -13,10 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @author max
*/
package com.intellij.psi.stubs;
import com.intellij.lang.Language;
@@ -47,11 +43,18 @@ import java.io.IOException;
import java.util.*;
import java.util.concurrent.Callable;
/*
* @author max
*/
public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtension<Integer, SerializedStubTree, FileContent> {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.stubs.StubUpdatingIndex");
public static final Key<CharSequence> FILE_TEXT_CONTENT_KEY = Key.create("file text content cached by stub indexer");
public static final ID<Integer, SerializedStubTree> INDEX_ID = ID.create("Stubs");
private static final int VERSION = 20;
private static final DataExternalizer<SerializedStubTree> KEY_EXTERNALIZER = new DataExternalizer<SerializedStubTree>() {
public void save(final DataOutput out, final SerializedStubTree v) throws IOException {
byte[] value = v.getBytes();
@@ -127,13 +130,14 @@ public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtensi
};
}
private static Key<StubElement> stubElementKey = Key.create("stub.tree.for.file.content");
private static final Key<StubElement> stubElementKey = Key.create("stub.tree.for.file.content");
@Nullable
public static StubElement buildStubTree(final FileContent inputData) {
StubElement data = inputData.getUserData(stubElementKey);
if (data != null) return data;
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (inputData) {
data = inputData.getUserData(stubElementKey);
if (data != null) return data;
@@ -152,15 +156,22 @@ public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtensi
final IFileElementType type = LanguageParserDefinitions.INSTANCE.forLanguage(l).getFileNodeType();
PsiFile psi = inputData.getPsiFile();
CharSequence contentAsText = inputData.getContentAsText();
psi.putUserData(FILE_TEXT_CONTENT_KEY, contentAsText);
if (type instanceof IStubFileElementType) {
data = ((IStubFileElementType)type).getBuilder().buildStubTree(psi);
try {
if (type instanceof IStubFileElementType) {
data = ((IStubFileElementType)type).getBuilder().buildStubTree(psi);
}
else if (filetype instanceof SubstitutedFileType) {
SubstitutedFileType substituted = (SubstitutedFileType) filetype;
LanguageFileType original = (LanguageFileType)substituted.getOriginalFileType();
final IFileElementType originalType = LanguageParserDefinitions.INSTANCE.forLanguage(original.getLanguage()).getFileNodeType();
data = ((IStubFileElementType)originalType).getBuilder().buildStubTree(psi);
}
}
else if (filetype instanceof SubstitutedFileType) {
SubstitutedFileType substituted = (SubstitutedFileType) filetype;
LanguageFileType original = (LanguageFileType)substituted.getOriginalFileType();
final IFileElementType originalType = LanguageParserDefinitions.INSTANCE.forLanguage(original.getLanguage()).getFileNodeType();
data = ((IStubFileElementType)originalType).getBuilder().buildStubTree(psi);
finally {
psi.putUserData(FILE_TEXT_CONTENT_KEY, null);
}
}
@@ -227,7 +238,7 @@ public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtensi
}
});
}
return new MyIndex(indexId, owner, storage, getIndexer());
return new MyIndex(indexId, storage, getIndexer());
}
private static void updateStubIndices(final Collection<StubIndexKey> indexKeys,
@@ -258,7 +269,6 @@ public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtensi
private final StubIndexImpl myStubIndex;
public MyIndex(final ID<Integer, SerializedStubTree> indexId,
final FileBasedIndex owner,
final IndexStorage<Integer, SerializedStubTree> storage,
final DataIndexer<Integer, SerializedStubTree, FileContent> indexer) {
super(indexId, indexer, storage);
@@ -268,7 +278,7 @@ public class StubUpdatingIndex extends CustomImplementationFileBasedIndexExtensi
}
catch (StorageException e) {
LOG.info(e);
owner.requestRebuild(INDEX_ID);
FileBasedIndex.requestRebuild(INDEX_ID);
}
}