diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringSymbolIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringSymbolIndex.java index e138dc9d4..eee386c9c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringSymbolIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringSymbolIndex.java @@ -56,6 +56,8 @@ import com.google.common.collect.ImmutableList; */ public class SpringSymbolIndex { + private static final int MAX_NUMBER_OF_SYMBOLS_IN_RESPONSE = 50; + private final SimpleLanguageServer server; private final BootLanguageServerParams params; private final JavaProjectFinder projectFinder; @@ -114,10 +116,10 @@ public class SpringSymbolIndex { this.params = params; this.projectFinder = params.projectFinder; - this.symbols = Collections.synchronizedList(new ArrayList<>()); + this.symbols = new ArrayList<>(); this.symbolsByDoc = new ConcurrentHashMap<>(); this.symbolsByProject = new ConcurrentHashMap<>(); - this.addonInformation = Collections.synchronizedList(new ArrayList<>()); + this.addonInformation = new ArrayList<>(); this.addonInformationByDoc = new ConcurrentHashMap<>(); this.addonInformationByProject = new ConcurrentHashMap<>(); @@ -325,29 +327,51 @@ public class SpringSymbolIndex { public List getAllSymbols(String query) { if (query != null && query.length() > 0) { - List foundSymbols = searchMatchingSymbols(this.symbols, query); - return foundSymbols.subList(0, Math.min(50, foundSymbols.size())); + synchronized(this.symbols) { + return searchMatchingSymbols(this.symbols, query, MAX_NUMBER_OF_SYMBOLS_IN_RESPONSE); + } } else { - return this.symbols.subList(0, Math.min(50, this.symbols.size())); + synchronized(this.symbols) { + List subList = this.symbols.subList(0, Math.min(MAX_NUMBER_OF_SYMBOLS_IN_RESPONSE, this.symbols.size())); + return new ArrayList(subList); + } } } public List getSymbols(String docURI) { - return this.symbolsByDoc.get(docURI); + List docSymbols = this.symbolsByDoc.get(docURI); + if (docSymbols != null) { + synchronized(docSymbols) { + return new ArrayList(docSymbols); + } + } + else { + return Collections.emptyList(); + } } public List getAllAdditionalInformation(Predicate filter) { if (filter != null) { - return addonInformation.stream().filter(filter).collect(Collectors.toList()); + synchronized(addonInformation) { + return addonInformation.stream().filter(filter).collect(Collectors.toList()); + } } else { - return null; + return Collections.emptyList(); } } public List getAdditonalInformation(String docURI) { List info = this.addonInformationByDoc.get(docURI); - return info == null ? ImmutableList.of() : info; + + if (info != null) { + synchronized(info) { + return new ArrayList<>(info); + } + } + else { + return Collections.emptyList(); + } } /** @@ -363,9 +387,10 @@ public class SpringSymbolIndex { }, this.updateQueue); } - private List searchMatchingSymbols(List allsymbols, String query) { + private List searchMatchingSymbols(List allsymbols, String query, int maxNumberOfSymbolsInResponse) { return allsymbols.stream() .filter(symbol -> StringUtil.containsCharactersCaseInsensitive(symbol.getName(), query)) + .limit(maxNumberOfSymbolsInResponse) .collect(Collectors.toList()); } @@ -469,38 +494,77 @@ public class SpringSymbolIndex { } private void addSymbol(IJavaProject project, String docURI, EnhancedSymbolInformation enhancedSymbol) { - symbols.add(enhancedSymbol.getSymbol()); - symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(enhancedSymbol.getSymbol()); - symbolsByProject.computeIfAbsent(project.getElementName(), s -> new ArrayList()).add(enhancedSymbol.getSymbol()); + synchronized(this.symbols) { + symbols.add(enhancedSymbol.getSymbol()); + } + + List docSymbols = symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()); + synchronized(docSymbols) { + docSymbols.add(enhancedSymbol.getSymbol()); + } + + List projectSymbols = symbolsByProject.computeIfAbsent(project.getElementName(), s -> new ArrayList()); + synchronized(projectSymbols) { + projectSymbols.add(enhancedSymbol.getSymbol()); + } if (enhancedSymbol.getAdditionalInformation() != null) { - addonInformation.addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); - addonInformationByDoc.computeIfAbsent(docURI, s -> new ArrayList()).addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); - addonInformationByProject.computeIfAbsent(project.getElementName(), s -> new ArrayList()).addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); + synchronized(addonInformation) { + addonInformation.addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); + } + + List infoByDoc = addonInformationByDoc.computeIfAbsent(docURI, s -> new ArrayList()); + synchronized(infoByDoc) { + infoByDoc.addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); + } + + List infoByProject = addonInformationByProject.computeIfAbsent(project.getElementName(), s -> new ArrayList()); + synchronized(infoByProject) { + infoByProject.addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); + } } } private void removeSymbolsByDoc(IJavaProject project, String docURI) { List oldSymbols = symbolsByDoc.remove(docURI); if (oldSymbols != null) { - symbols.removeAll(oldSymbols); + + List copy = null; + synchronized(oldSymbols) { + copy = new ArrayList<>(oldSymbols); + } + + synchronized(this.symbols) { + this.symbols.removeAll(copy); + } List projectSymbols = symbolsByProject.get(project.getElementName()); if (projectSymbols != null) { - projectSymbols.removeAll(oldSymbols); + synchronized(projectSymbols) { + projectSymbols.removeAll(copy); + } } } - List oldAddInInformation = addonInformationByDoc.remove(docURI); - if (oldAddInInformation != null) { - addonInformation.removeAll(oldAddInInformation); + List oldAddOnInformation = addonInformationByDoc.remove(docURI); + if (oldAddOnInformation != null) { + + List copy = null; + synchronized(oldAddOnInformation) { + copy = new ArrayList<>(oldAddOnInformation); + } + + synchronized(addonInformation) { + addonInformation.removeAll(copy); + } List projectAddOns = addonInformationByProject.get(project.getElementName()); if (projectAddOns != null) { - projectAddOns.removeAll(oldAddInInformation); + synchronized(projectAddOns) { + projectAddOns.removeAll(copy); + } } } - } private void removeSymbolsByProject(IJavaProject project) { @@ -510,34 +574,55 @@ public class SpringSymbolIndex { } List oldSymbols = symbolsByProject.remove(project.getElementName()); if (oldSymbols != null) { - symbols.removeAll(oldSymbols); + + List copy = null; + synchronized(oldSymbols) { + copy = new ArrayList<>(oldSymbols); + } + + synchronized(this.symbols) { + symbols.removeAll(copy); + } Set keySet = symbolsByDoc.keySet(); Iterator docIter = keySet.iterator(); while (docIter.hasNext()) { String docURI = docIter.next(); List docSymbols = symbolsByDoc.get(docURI); - docSymbols.removeAll(oldSymbols); + synchronized(docSymbols) { + docSymbols.removeAll(copy); - if (docSymbols.isEmpty()) { - docIter.remove(); + if (docSymbols.isEmpty()) { + docIter.remove(); + } } } } List oldAddInInformation = addonInformationByProject.remove(project.getElementName()); if (oldAddInInformation != null) { - addonInformation.removeAll(oldAddInInformation); + + List copy = null; + synchronized(oldAddInInformation) { + copy = new ArrayList<>(oldAddInInformation); + } + + synchronized(this.addonInformation) { + addonInformation.removeAll(copy); + } Set keySet = addonInformationByDoc.keySet(); Iterator docIter = keySet.iterator(); while (docIter.hasNext()) { String docURI = docIter.next(); List docAddons = addonInformationByDoc.get(docURI); - docAddons.removeAll(oldAddInInformation); - if (docAddons.isEmpty()) { - docIter.remove(); + synchronized(docAddons) { + docAddons.removeAll(copy); + + if (docAddons.isEmpty()) { + docIter.remove(); + } } } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java index 840d3323b..200a0a06b 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2018 Pivotal, Inc. + * Copyright (c) 2017, 2019 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 @@ -12,6 +12,7 @@ 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.assertNull; import static org.junit.Assert.assertTrue; @@ -180,7 +181,8 @@ public class SpringIndexerTest { // check for document to not be created yet List symbols = indexer.getSymbols(createdDocURI); - assertNull(symbols); + assertNotNull(symbols); + assertEquals(0, symbols.size()); List allSymbols = indexer.getAllSymbols(""); assertEquals(7, allSymbols.size());