initial change towards multi-file symbol indexing on change events

This commit is contained in:
Martin Lippert
2020-01-17 15:27:07 +01:00
parent 601006c53b
commit 6413bcb7cc
18 changed files with 722 additions and 144 deletions

View File

@@ -88,6 +88,8 @@ public class RequestMappingDependentConstantChangedTest {
indexer.getJavaIndexer().setFileScanListener(fileScanListener);
replaceInFile(constantsUri, "path/from/constant", "/changed-path");
indexer.updateDocument(constantsUri, null, "triggered by test code").get();
fileScanListener.assertScannedUris(constantsUri, docUri);
fileScanListener.assertScannedUri(constantsUri, 1);
fileScanListener.assertScannedUri(docUri, 1);
@@ -97,7 +99,53 @@ public class RequestMappingDependentConstantChangedTest {
assertSymbol(docUri, "@/changed-path", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)");
}
@Test public void testCyclicalDependency() throws Exception {
@Test
public void testSimpleRequestMappingSymbolFromConstantInDifferentClassViaMultipleFilesUpdate() throws Exception {
String docUri = directory.resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString();
String constantsUri = directory.resolve("src/main/java/org/test/Constants.java").toUri().toString();
List<? extends SymbolInformation> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertSymbol(docUri, "@/path/from/constant", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)");
TestFileScanListener fileScanListener = new TestFileScanListener();
indexer.getJavaIndexer().setFileScanListener(fileScanListener);
replaceInFile(constantsUri, "path/from/constant", "/changed-path");
indexer.updateDocuments(new String[] {constantsUri}, "triggered by test code").get();
fileScanListener.assertScannedUris(constantsUri, docUri);
fileScanListener.assertScannedUri(constantsUri, 1);
fileScanListener.assertScannedUri(docUri, 1);
symbols = indexer.getSymbols(docUri);
assertSymbolCount(1, symbols);
assertSymbol(docUri, "@/changed-path", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)");
}
@Test
public void testRequestMappingSymbolFromConstantChained() throws Exception {
String docUri = directory.resolve("src/main/java/org/test/ChainedRequestMappingPathOverMultipleClasses.java").toUri().toString();
String chainConstantsUri_2 = directory.resolve("src/main/java/org/test/ChainElement2.java").toUri().toString();
List<? extends SymbolInformation> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertSymbol(docUri, "@/path/from/chain", "@RequestMapping(ChainElement1.MAPPING_PATH_1)");
replaceInFile(chainConstantsUri_2, "path/from/chain", "/changed-path");
indexer.updateDocument(chainConstantsUri_2, null, "triggered by test code").get();
symbols = indexer.getSymbols(docUri);
assertSymbolCount(1, symbols);
assertSymbol(docUri, "@/path/from/chain", "@RequestMapping(ChainElement1.MAPPING_PATH_1)");
// You would expect here that the symbol got updated from "path/from/chain" to the changed value "/changed-path",
// but the mechanism doesn't know anything about this chained dependendy. This is a limitation of the current
// implementation, since the AST has no idea about the chain, therefore we are only aware of the first
// element in this chained dependency, which comes from ChainElement1.java
}
@Test
public void testCyclicalDependency() throws Exception {
//cyclical dependency between two files (ping refers pong and vice versa)
String pingUri = directory.resolve("src/main/java/org/test/PingConstantRequestMapping.java").toUri().toString();
@@ -118,6 +166,43 @@ public class RequestMappingDependentConstantChangedTest {
}
replaceInFile(pingUri, "/ping", "/changed");
indexer.updateDocument(pingUri, null, "triggered by test code").get();
{
List<? extends SymbolInformation> symbols = indexer.getSymbols(pingUri);
assertSymbolCount(1, symbols);
assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)");
}
{
List<? extends SymbolInformation> symbols = indexer.getSymbols(pongUri);
assertSymbolCount(1, symbols);
assertSymbol(pongUri, "@/changed -- GET", "@GetMapping(PingConstantRequestMapping.PING)");
}
}
@Test
public void testCyclicalDependencyViaMultipleFilesUpdate() throws Exception {
//cyclical dependency between two files (ping refers pong and vice versa)
String pingUri = directory.resolve("src/main/java/org/test/PingConstantRequestMapping.java").toUri().toString();
String pongUri = directory.resolve("src/main/java/org/test/PongConstantRequestMapping.java").toUri().toString();
{
List<? extends SymbolInformation> symbols = indexer.getSymbols(pingUri);
for (SymbolInformation s : symbols) {
System.out.println(s.getName());
}
assertSymbolCount(1, symbols);
assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)");
}
{
List<? extends SymbolInformation> symbols = indexer.getSymbols(pongUri);
assertSymbolCount(1, symbols);
assertSymbol(pongUri, "@/ping -- GET", "@GetMapping(PingConstantRequestMapping.PING)");
}
replaceInFile(pingUri, "/ping", "/changed");
indexer.updateDocuments(new String[] {pingUri}, "triggered by test code").get();
{
List<? extends SymbolInformation> symbols = indexer.getSymbols(pingUri);
@@ -167,7 +252,5 @@ public class RequestMappingDependentConstantChangedTest {
assertTrue(oldContent.contains(find));
String newContent = oldContent.replace(find, replace);
FileUtils.write(target, newContent, "UTF8");
indexer.updateDocument(docUri, null, "triggered by test code").get();
}
}

View File

@@ -32,7 +32,7 @@ import org.springframework.context.annotation.Import;
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.DependencyTracker;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaDependencyTracker;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.util.UriUtil;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
@@ -88,7 +88,7 @@ public class RequestMappingSymbolProviderTest {
assertTrue(containsSymbol(symbols, "@/path/from/constant", docUri, 6, 1, 6, 48));
//Verify whether dependency tracker logics works properly for this example.
DependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker();
SpringIndexerJavaDependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker();
assertEquals(ImmutableSet.of("Lorg/test/Constants;"), dt.getAllDependencies().get(UriUtil.toFileString(docUri)));
TestFileScanListener fileScanListener = new TestFileScanListener();

View File

@@ -0,0 +1,180 @@
/*******************************************************************************
* Copyright (c) 2020 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.utils.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.net.URI;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
import org.springframework.ide.vscode.boot.java.utils.SymbolIndexConfig;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author Martin Lippert
*/
@RunWith(SpringRunner.class)
@BootLanguageServerTest
@Import(SymbolProviderTestConf.class)
public class SpringIndexerMultipleFilesTest {
@Autowired private BootLanguageServerHarness harness;
@Autowired private SpringSymbolIndex indexer;
@Autowired private JavaProjectFinder projectFinder;
private File directory;
private String projectDir;
private IJavaProject project;
@Before
public void setup() throws Exception {
harness.intialize(null);
indexer.configureIndexer(SymbolIndexConfig.builder().scanXml(false).build());
directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI());
projectDir = directory.toURI().toString();
// trigger project creation
project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
CompletableFuture<Void> initProject = indexer.waitOperation();
initProject.get(5, TimeUnit.SECONDS);
}
@Test
public void testUpdateChangedSingleDocumentOnDisc() throws Exception {
String changedDocURI = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString();
String originalContent = FileUtils.readFileToString(new File(new URI(changedDocURI)));
try {
// update document and update index
assertTrue(containsSymbol(indexer.getSymbols(changedDocURI), "@/mapping1", changedDocURI));
String newContent = originalContent.replace("mapping1", "mapping1-CHANGED");
FileUtils.writeStringToFile(new File(new URI(changedDocURI)), newContent);
CompletableFuture<Void> updateFuture = indexer.updateDocument(changedDocURI, null, "test triggered");
updateFuture.get(5, TimeUnit.SECONDS);
// check for updated index per document
List<? extends SymbolInformation> symbols = indexer.getSymbols(changedDocURI);
assertEquals(2, symbols.size());
assertTrue(containsSymbol(symbols, "@/mapping1-CHANGED", changedDocURI, 6, 1, 6, 36));
assertTrue(containsSymbol(symbols, "@/mapping2", changedDocURI, 11, 1, 11, 28));
}
finally {
FileUtils.writeStringToFile(new File(new URI(changedDocURI)), originalContent);
}
}
@Test
public void testUpdateChangedMultipleDocumentsOnDisc() throws Exception {
String doc1URI = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString();
String original1Content = FileUtils.readFileToString(new File(new URI(doc1URI)));
String doc2URI = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString();
String original2Content = FileUtils.readFileToString(new File(new URI(doc2URI)));
String doc3URI = directory.toPath().resolve("src/main/java/org/test/sub/MappingClassSubpackage.java").toUri().toString();
String original3Content = FileUtils.readFileToString(new File(new URI(doc3URI)));
try {
String new1Content = original1Content.replace("mapping1", "mapping1-CHANGED");
FileUtils.writeStringToFile(new File(new URI(doc1URI)), new1Content);
String new2Content = original2Content.replace("\"/embedded-foo-mapping\"", "\"/embedded-foo-mapping-CHANGED\"");
FileUtils.writeStringToFile(new File(new URI(doc2URI)), new2Content);
String new3Content = original3Content.replace("classlevel", "classlevel-CHANGED");
FileUtils.writeStringToFile(new File(new URI(doc3URI)), new3Content);
CompletableFuture<Void> updateFuture = indexer.updateDocuments(new String[] {doc1URI, doc2URI, doc3URI}, "test triggered");
updateFuture.get(5, TimeUnit.SECONDS);
// check for updated index per document
List<? extends SymbolInformation> symbols1 = indexer.getSymbols(doc1URI);
assertEquals(2, symbols1.size());
assertTrue(containsSymbol(symbols1, "@/mapping1-CHANGED", doc1URI, 6, 1, 6, 36));
assertTrue(containsSymbol(symbols1, "@/mapping2", doc1URI, 11, 1, 11, 28));
List<? extends SymbolInformation> symbols2 = indexer.getSymbols(doc2URI);
assertTrue(containsSymbol(symbols2, "@+ 'mainClass' (@SpringBootApplication <: @SpringBootConfiguration, @Configuration, @Component) MainClass", doc2URI, 6, 0, 6, 22));
assertTrue(containsSymbol(symbols2, "@/embedded-foo-mapping-CHANGED", doc2URI, 17, 1, 17, 49));
assertTrue(containsSymbol(symbols2, "@/foo-root-mapping/embedded-foo-mapping-with-root", doc2URI, 27, 1, 27, 51));
List<? extends SymbolInformation> symbols3 = indexer.getSymbols(doc3URI);
assertTrue(containsSymbol(symbols3, "@/classlevel-CHANGED/mapping-subpackage", doc3URI, 7, 1, 7, 38));
}
finally {
FileUtils.writeStringToFile(new File(new URI(doc1URI)), original1Content);
FileUtils.writeStringToFile(new File(new URI(doc2URI)), original2Content);
FileUtils.writeStringToFile(new File(new URI(doc3URI)), original3Content);
}
}
private boolean containsSymbol(List<? extends SymbolInformation> symbols, String name, String uri) {
for (Iterator<? extends SymbolInformation> iterator = symbols.iterator(); iterator.hasNext();) {
SymbolInformation symbol = iterator.next();
if (
symbol.getName().equals(name) &&
symbol.getLocation().getUri().equals(uri)
) {
return true;
}
}
return false;
}
private boolean containsSymbol(List<? extends SymbolInformation> symbols, String name, String uri, int startLine, int startCHaracter, int endLine, int endCharacter) {
for (Iterator<? extends SymbolInformation> iterator = symbols.iterator(); iterator.hasNext();) {
SymbolInformation symbol = iterator.next();
if (symbol.getName().equals(name)
&& symbol.getLocation().getUri().equals(uri)
&& symbol.getLocation().getRange().getStart().getLine() == startLine
&& symbol.getLocation().getRange().getStart().getCharacter() == startCHaracter
&& symbol.getLocation().getRange().getEnd().getLine() == endLine
&& symbol.getLocation().getRange().getEnd().getCharacter() == endCharacter) {
return true;
}
}
return false;
}
}

View File

@@ -1,22 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
<project xmlns="https://maven.apache.org/POM/4.0.0"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>test-annotation-indexing-non-boot-project</artifactId>
<groupId>test-projects</groupId>
<version>5.1.3</version>
<groupId>test-projects</groupId>
<version>5.1.3</version>
<packaging>jar</packaging>
<name>test-annotation-indexing-non-boot-project</name>
<description>Test projects for regular non-boot spring project - annotation indexing</description>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,22 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
<project xmlns="https://maven.apache.org/POM/4.0.0"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>test-annotation-indexing-non-boot-project</artifactId>
<groupId>test-projects</groupId>
<version>5.1.3</version>
<groupId>test-projects</groupId>
<version>5.1.3</version>
<packaging>jar</packaging>
<name>test-annotation-indexing-non-boot-project</name>
<description>Test projects for regular non-boot spring project - annotation indexing</description>
<properties>
<maven.javadoc.skip>true</maven.javadoc.skip>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,7 @@
package org.test;
public class ChainElement1 {
public static final String MAPPING_PATH_1 = ChainElement2.MAPPING_PATH_2;
}

View File

@@ -0,0 +1,7 @@
package org.test;
public class ChainElement2 {
public static final String MAPPING_PATH_2 = "path/from/chain";
}

View File

@@ -0,0 +1,12 @@
package org.test;
import org.springframework.web.bind.annotation.RequestMapping;
public class ChainedRequestMappingOverMultipleClasses {
@RequestMapping(ChainElement1.MAPPING_PATH_1)
public String hello() {
return "Hello";
}
}