diff --git a/headless-services/boot-java-language-server/pom.xml b/headless-services/boot-java-language-server/pom.xml index d1c813b6c..33a4cf809 100644 --- a/headless-services/boot-java-language-server/pom.xml +++ b/headless-services/boot-java-language-server/pom.xml @@ -84,7 +84,7 @@ commons-io commons-io - 2.4 + ${commons-io-version} diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java index 9dc13e806..36e3aa6d8 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/AutowiredHoverProvider.java @@ -25,6 +25,8 @@ import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -54,8 +56,8 @@ public class AutowiredHoverProvider implements HoverProvider { for (SpringBootAppProvider bootApp : runningApps) { try { - String liveBeans = bootApp.getBeans(); - if (liveBeans != null && liveBeans.length() > 0) { + LiveBeansModel liveBeans = bootApp.getBeans(); + if (liveBeans != null && !liveBeans.isEmpty()) { addLiveHoverContent(annotation, doc, liveBeans, bootApp, hoverContent); } } @@ -85,8 +87,8 @@ public class AutowiredHoverProvider implements HoverProvider { try { for (SpringBootApp bootApp : runningApps) { try { - String liveBeans = bootApp.getBeans(); - if (liveBeans != null && liveBeans.length() > 0) { + LiveBeansModel liveBeans = bootApp.getBeans(); + if (liveBeans != null && !liveBeans.isEmpty()) { Range range = getLiveHoverHint(annotation, doc, liveBeans); if (range != null) { return ImmutableList.of(range); @@ -105,14 +107,11 @@ public class AutowiredHoverProvider implements HoverProvider { return null; } - public Range getLiveHoverHint(Annotation annotation, TextDocument doc, String liveBeansJSON) { + public Range getLiveHoverHint(Annotation annotation, TextDocument doc, LiveBeansModel beansModel) { try { String type = findDeclaredType(annotation); - if (type != null && liveBeansJSON != null) { - - LiveBeansModel beansModel = LiveBeansModel.parse(liveBeansJSON); + if (type != null && beansModel != null) { LiveBean[] beansOfType = beansModel.getBeansOfType(type); - if (beansOfType.length > 0) { Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); return hoverRange; @@ -126,11 +125,9 @@ public class AutowiredHoverProvider implements HoverProvider { return null; } - public void addLiveHoverContent(Annotation annotation, TextDocument doc, String liveBeansJSON, SpringBootAppProvider bootApp, List> hoverContent) { + public void addLiveHoverContent(Annotation annotation, TextDocument doc, LiveBeansModel beansModel, SpringBootAppProvider bootApp, List> hoverContent) { String type = findDeclaredType(annotation); - if (type != null && liveBeansJSON != null) { - - LiveBeansModel beansModel = LiveBeansModel.parse(liveBeansJSON); + if (type != null && beansModel != null) { LiveBean[] beansOfType = beansModel.getBeansOfType(type); if (beansOfType.length > 0) { diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/LiveBeansModel.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/LiveBeansModel.java deleted file mode 100644 index 85071675c..000000000 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/LiveBeansModel.java +++ /dev/null @@ -1,88 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 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 - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Pivotal, Inc. - initial API and implementation - *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.autowired; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -/** - * @author Martin Lippert - */ -public class LiveBeansModel { - - public static LiveBeansModel parse(String json) { - LiveBeansModel model = new LiveBeansModel(); - - try { - JSONArray mainArray = new JSONArray(json); - - for (int i = 0; i < mainArray.length(); i++) { - JSONObject appContext = mainArray.getJSONObject(i); - if (appContext == null) continue; - - JSONArray beansArray = appContext.optJSONArray("beans"); - if (beansArray == null) continue; - - for (int j = 0; j < beansArray.length(); j++) { - JSONObject beanObject = beansArray.getJSONObject(j); - if (beanObject == null) continue; - - LiveBean bean = LiveBean.parse(beanObject); - if (bean != null) { - model.add(bean); - } - } - } - } - catch (JSONException e) { - e.printStackTrace(); - } - - return model; - } - - private final ConcurrentMap> beansViaType; - private final ConcurrentMap> beansViaName; - - protected LiveBeansModel() { - this.beansViaType = new ConcurrentHashMap<>(); - this.beansViaName = new ConcurrentHashMap<>(); - } - - public LiveBean[] getBeansOfType(String fullyQualifiedType) { - List result = beansViaType.get(fullyQualifiedType); - return result != null ? result.toArray(new LiveBean[result.size()]) : new LiveBean[0]; - } - - public LiveBean[] getBeansOfName(String beanName) { - List result = beansViaName.get(beanName); - return result != null ? result.toArray(new LiveBean[result.size()]) : new LiveBean[0]; - } - - protected void add(LiveBean bean) { - String type = bean.getType(); - if (type != null) { - beansViaType.computeIfAbsent(type, (t) -> new ArrayList<>()).add(bean); - } - - String name = bean.getId(); - if (name != null) { - beansViaName.computeIfAbsent(name, (n) -> new ArrayList<>()).add(bean); - } - } - -} diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProvider.java index 9a4a64d3e..5bf1fda5d 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProvider.java @@ -10,12 +10,14 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.autowired; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; + /** * @author Martin Lippert */ public interface SpringBootAppProvider { - public String getBeans() throws Exception; + public LiveBeansModel getBeans() throws Exception; public String getProcessID(); public String getProcessName(); diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProviderImpl.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProviderImpl.java index af8e94c16..3563d0376 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProviderImpl.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/SpringBootAppProviderImpl.java @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.boot.java.autowired; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; /** * @author Martin Lippert @@ -24,7 +25,7 @@ public class SpringBootAppProviderImpl implements SpringBootAppProvider { } @Override - public String getBeans() throws Exception { + public LiveBeansModel getBeans() throws Exception { return bootApp.getBeans(); } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentHoverProvider.java index 4a09f215b..4852b1d0d 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/ComponentHoverProvider.java @@ -26,12 +26,12 @@ import org.eclipse.lsp4j.MarkedString; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.springframework.ide.vscode.boot.java.autowired.Constants; -import org.springframework.ide.vscode.boot.java.autowired.LiveBean; -import org.springframework.ide.vscode.boot.java.autowired.LiveBeansModel; import org.springframework.ide.vscode.boot.java.autowired.SpringBootAppProvider; import org.springframework.ide.vscode.boot.java.autowired.SpringBootAppProviderImpl; import org.springframework.ide.vscode.boot.java.handlers.HoverProvider; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -68,8 +68,8 @@ public class ComponentHoverProvider implements HoverProvider { List> hoverContent = new ArrayList<>(); for (SpringBootAppProvider bootApp : runningApps) { try { - String liveBeans = bootApp.getBeans(); - if (liveBeans != null && liveBeans.length() > 0) { + LiveBeansModel liveBeans = bootApp.getBeans(); + if (liveBeans != null && !liveBeans.isEmpty()) { addLiveHoverContent(typeDecl, doc, liveBeans, bootApp, hoverContent); } } @@ -101,8 +101,8 @@ public class ComponentHoverProvider implements HoverProvider { try { for (SpringBootApp bootApp : runningApps) { try { - String liveBeans = bootApp.getBeans(); - if (liveBeans != null && liveBeans.length() > 0) { + LiveBeansModel liveBeans = bootApp.getBeans(); + if (liveBeans != null && !liveBeans.isEmpty()) { Range range = getLiveHoverHint(annotation, doc, liveBeans); if (range != null) { return ImmutableList.of(range); @@ -121,13 +121,11 @@ public class ComponentHoverProvider implements HoverProvider { return null; } - public Range getLiveHoverHint(Annotation annotation, TextDocument doc, String liveBeansJSON) { + public Range getLiveHoverHint(Annotation annotation, TextDocument doc, LiveBeansModel beansModel) { try { TypeDeclaration type = findDeclaredType(annotation); - if (type != null && liveBeansJSON != null) { + if (type != null && beansModel != null) { String typeName = type.resolveBinding().getQualifiedName(); - - LiveBeansModel beansModel = LiveBeansModel.parse(liveBeansJSON); LiveBean[] beansOfType = beansModel.getBeansOfType(typeName); if (beansOfType.length > 0) { @@ -160,11 +158,9 @@ public class ComponentHoverProvider implements HoverProvider { } - public void addLiveHoverContent(TypeDeclaration declaringType, TextDocument doc, String liveBeansJSON, SpringBootAppProvider bootApp, List> hoverContent) { + public void addLiveHoverContent(TypeDeclaration declaringType, TextDocument doc, LiveBeansModel beansModel, SpringBootAppProvider bootApp, List> hoverContent) { String type = declaringType.resolveBinding().getQualifiedName(); - if (type != null && liveBeansJSON != null) { - - LiveBeansModel beansModel = LiveBeansModel.parse(liveBeansJSON); + if (type != null && beansModel != null) { LiveBean[] beansOfType = beansModel.getBeansOfType(type); if (beansOfType.length > 0) { diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java index 83a256ba8..a5a1b1c45 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/conditionals/ConditionalsLiveHoverProvider.java @@ -96,9 +96,20 @@ public class ConditionalsLiveHoverProvider implements HoverProvider { private void addHoverContent(List conditions, List> hoverContent) throws Exception { - for (RunningAppConditional condition : conditions) { + for (int i = 0; i < conditions.size(); i++) { + RunningAppConditional condition = conditions.get(i); hoverContent.add(Either.forLeft("Condition: " + condition.condition)); hoverContent.add(Either.forLeft("Message: " + condition.message)); + + // If there is more than one instances show process information + if (conditions.size() > 1) { + hoverContent.add(Either.forLeft("Process ID: " + condition.app.getProcessID())); + hoverContent.add(Either.forLeft("Process Name: " + condition.app.getProcessName())); + } + + if (i < conditions.size() - 1) { + hoverContent.add(Either.forLeft("---")); + } } } @@ -117,16 +128,21 @@ public class ConditionalsLiveHoverProvider implements HoverProvider { public Optional> parse(Annotation annotation, SpringBootApp[] runningApps) { try { + List allConditionals = new ArrayList<>(); for (SpringBootApp app : runningApps) { String autoConfigRecord = app.getAutoConfigReport(); if (autoConfigRecord != null) { JSONObject autoConfigJson = new JSONObject(autoConfigRecord); - List conditionalsFromPositiveMatches = getConditionals(annotation, autoConfigJson); - if(!conditionalsFromPositiveMatches.isEmpty()) { - return Optional.of(conditionalsFromPositiveMatches); + List conditionalsFromPositiveMatches = getConditionals(app, annotation, + autoConfigJson); + if (!conditionalsFromPositiveMatches.isEmpty()) { + allConditionals.addAll(conditionalsFromPositiveMatches); } } } + if (!allConditionals.isEmpty()) { + return Optional.of(allConditionals); + } } catch (Exception e) { Log.log(e); } @@ -135,12 +151,13 @@ public class ConditionalsLiveHoverProvider implements HoverProvider { /** * + * @param app * @param annotation * @param autoConfigJson * @return non-null list of conditionals parsed from an autoconfig report. List * may be empty. */ - private List getConditionals(Annotation annotation, + private List getConditionals(SpringBootApp app, Annotation annotation, JSONObject autoConfigJson) { List conditions = new ArrayList<>(); @@ -152,7 +169,7 @@ public class ConditionalsLiveHoverProvider implements HoverProvider { JSONArray matchList = (JSONArray) positiveMatches.get(positiveMatchKey); matchList.forEach((match) -> { if (match instanceof JSONObject) { - getMatchedCondition((JSONObject) match, annotation) + getMatchedCondition(app, (JSONObject) match, annotation) .ifPresent((condition) -> conditions.add(condition)); } }); @@ -203,13 +220,14 @@ public class ConditionalsLiveHoverProvider implements HoverProvider { return false; } - protected Optional getMatchedCondition(JSONObject conditionJson, Annotation annotation) { + protected Optional getMatchedCondition(SpringBootApp app, JSONObject conditionJson, + Annotation annotation) { if (conditionJson != null) { String condition = (String) conditionJson.get("condition"); String message = (String) conditionJson.get("message"); String annotationName = annotation.resolveTypeBinding().getName(); if (message.contains(annotationName)) { - return Optional.of(new RunningAppConditional(condition, message)); + return Optional.of(new RunningAppConditional(app, condition, message)); } } return Optional.empty(); @@ -220,10 +238,12 @@ public class ConditionalsLiveHoverProvider implements HoverProvider { public final String condition; public final String message; + public final SpringBootApp app; - public RunningAppConditional(String condition, String message) { + public RunningAppConditional(SpringBootApp app, String condition, String message) { this.condition = condition; this.message = message; + this.app = app; } } } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java index d39e0baff..87930d291 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingHoverProvider.java @@ -13,27 +13,15 @@ package org.springframework.ide.vscode.boot.java.requestmapping; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.ArrayInitializer; -import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.NormalAnnotation; -import org.eclipse.jdt.core.dom.QualifiedName; -import org.eclipse.jdt.core.dom.SimpleName; -import org.eclipse.jdt.core.dom.SingleMemberAnnotation; -import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.MarkedString; @@ -66,7 +54,7 @@ public class RequestMappingHoverProvider implements HoverProvider { public Collection getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { try { if (runningApps.length > 0) { - Optional> val = getRequestMappingMethodFromRunningApp(annotation, runningApps); + Optional>> val = getRequestMappingMethodFromRunningApp(annotation, runningApps); if (val.isPresent()) { Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength()); return ImmutableList.of(hoverRange); @@ -85,7 +73,7 @@ public class RequestMappingHoverProvider implements HoverProvider { try { List> hoverContent = new ArrayList<>(); - Optional> val = getRequestMappingMethodFromRunningApp(annotation, runningApps); + Optional>> val = getRequestMappingMethodFromRunningApp(annotation, runningApps); if (val.isPresent()) { addHoverContent(val.get(), hoverContent); @@ -105,20 +93,22 @@ public class RequestMappingHoverProvider implements HoverProvider { return null; } - private Optional> getRequestMappingMethodFromRunningApp(Annotation annotation, + private Optional>> getRequestMappingMethodFromRunningApp(Annotation annotation, SpringBootApp[] runningApps) { try { + List> results = new ArrayList<>(); for (SpringBootApp app : runningApps) { Collection mappings = app.getRequestMappings(); - if (mappings!=null && !mappings.isEmpty()) { - return mappings.stream() - .filter(rm -> matchesAnnotation(annotation, rm)) + if (mappings != null && !mappings.isEmpty()) { + mappings.stream() +// .filter(rm -> matchesAnnotation(annotation, rm)) .filter(rm -> methodMatchesAnnotation(annotation, rm)) .map(rm -> Tuples.of(rm, app)) - .findFirst(); + .findFirst().ifPresent(t -> results.add(t)); } } + return Optional.of(results); } catch (Exception e) { Log.log(e); } @@ -135,7 +125,7 @@ public class RequestMappingHoverProvider implements HoverProvider { return binding.getDeclaringClass().getQualifiedName().equals(rqClassName) && binding.getName().equals(rm.getMethodName()) && Arrays.equals(Arrays.stream(binding.getParameterTypes()) - .map(t -> t.getQualifiedName()) + .map(t -> t.getTypeDeclaration().getQualifiedName()) .toArray(String[]::new), rm.getMethodParameters()); } else if (parent instanceof TypeDeclaration) { @@ -145,157 +135,154 @@ public class RequestMappingHoverProvider implements HoverProvider { return false; } - private void addHoverContent(Tuple2 mappingMethod, List> hoverContent) throws Exception { - String processId = mappingMethod.getT2().getProcessID(); - String processName = mappingMethod.getT2().getProcessName(); - String port = mappingMethod.getT2().getPort(); - String host = mappingMethod.getT2().getHost(); - StringBuilder builder = new StringBuilder(); + private void addHoverContent(List> mappingMethods, List> hoverContent) throws Exception { + for (int i = 0; i < mappingMethods.size(); i++) { + Tuple2 mappingMethod = mappingMethods.get(i); - Arrays.stream(mappingMethod.getT1().getSplitPath()).forEach(path -> { + String processId = mappingMethod.getT2().getProcessID(); + String processName = mappingMethod.getT2().getProcessName(); + String port = mappingMethod.getT2().getPort(); + String host = mappingMethod.getT2().getHost(); + StringBuilder builder = new StringBuilder(); - String url = UrlUtil.createUrl(host, port, path); + Arrays.stream(mappingMethod.getT1().getSplitPath()).forEach(path -> { - if (builder.length() > 0) { - builder.append("\n"); - } - builder.append("Path: "); - builder.append("["); - builder.append(path); - builder.append("]"); - builder.append("("); - builder.append(url); - builder.append(")"); + String url = UrlUtil.createUrl(host, port, path); - }); - - hoverContent.add(Either.forLeft(builder.toString())); - hoverContent.add(Either.forLeft("Process ID: " + processId)); - hoverContent.add(Either.forLeft("Process Name: " + processName)); - } - - protected String getRequestMethod(SingleMemberAnnotation annotation) { - ITypeBinding type = annotation.resolveTypeBinding(); - if (type != null) { - switch (type.getQualifiedName()) { - case Constants.SPRING_GET_MAPPING: - return "GET"; - case Constants.SPRING_POST_MAPPING: - return "POST"; - case Constants.SPRING_DELETE_MAPPING: - return "DELETE"; - case Constants.SPRING_PUT_MAPPING: - return "PUT"; - case Constants.SPRING_PATCH_MAPPING: - return "PATCH"; - } - } - return null; - } - - private boolean matchesAnnotation(Annotation annotation, RequestMapping rm) { - String[] mappingPath = null; - Set methods = null; - if (annotation instanceof SingleMemberAnnotation) { - SingleMemberAnnotation singleAnnotation = (SingleMemberAnnotation) annotation; - Expression valueContent = singleAnnotation.getValue(); - if (valueContent instanceof StringLiteral) { - mappingPath = new String[] { ((StringLiteral)valueContent).getLiteralValue() }; - } - String method = getRequestMethod(singleAnnotation); - if (method != null) { - methods = new HashSet<>(); - methods.add(method); - } - } - else if (annotation instanceof NormalAnnotation) { - List values = ((NormalAnnotation) annotation).values(); - for (Object value : values) { - if (value instanceof MemberValuePair) { - MemberValuePair pair = (MemberValuePair)value; - String name = pair.getName().toString(); - switch (name) { - case "value": - case "path": - mappingPath = getPaths(pair.getValue()); - break; - case "method": - methods = getRequestMethod(pair.getValue()); - break; - } + if (builder.length() > 0) { + builder.append("\n"); } + builder.append("["); + builder.append(url); + builder.append("]"); + builder.append("("); + builder.append(url); + builder.append(")"); + + }); + + hoverContent.add(Either.forLeft(builder.toString())); + hoverContent.add(Either.forLeft("Process ID: " + processId)); + hoverContent.add(Either.forLeft("Process Name: " + processName)); + if (i < mappingMethods.size() - 1) { + // Three dashes == line separator in Markdown + hoverContent.add(Either.forLeft("---")); } } - - if (mappingPath != null) { - if (Arrays.equals(mappingPath, rm.getSplitPath())) { - if (methods == null || methods.isEmpty()) { - return true; - } else { - return methods.equals(rm.getRequestMethods()); - } - } - } - return false; - } - private static String getExpressionValueAsString(Expression exp) { - if (exp instanceof StringLiteral) { - return ((StringLiteral)exp).getLiteralValue(); - } else if (exp instanceof QualifiedName) { - return getExpressionValueAsString(((QualifiedName)exp).getName()); - } else if (exp instanceof SimpleName) { - return ((SimpleName)exp).getIdentifier(); - } else { - return null; - } - } +// protected String getRequestMethod(SingleMemberAnnotation annotation) { +// ITypeBinding type = annotation.resolveTypeBinding(); +// if (type != null) { +// switch (type.getQualifiedName()) { +// case Constants.SPRING_GET_MAPPING: +// return "GET"; +// case Constants.SPRING_POST_MAPPING: +// return "POST"; +// case Constants.SPRING_DELETE_MAPPING: +// return "DELETE"; +// case Constants.SPRING_PUT_MAPPING: +// return "PUT"; +// case Constants.SPRING_PATCH_MAPPING: +// return "PATCH"; +// } +// } +// return null; +// } +// +// private boolean matchesAnnotation(Annotation annotation, RequestMapping rm) { +// String[] mappingPath = null; +// Set methods = null; +// if (annotation instanceof SingleMemberAnnotation) { +// SingleMemberAnnotation singleAnnotation = (SingleMemberAnnotation) annotation; +// Expression valueContent = singleAnnotation.getValue(); +// if (valueContent instanceof StringLiteral) { +// mappingPath = new String[] { ((StringLiteral)valueContent).getLiteralValue() }; +// } +// String method = getRequestMethod(singleAnnotation); +// if (method != null) { +// methods = new HashSet<>(); +// methods.add(method); +// } +// } +// else if (annotation instanceof NormalAnnotation) { +// List values = ((NormalAnnotation) annotation).values(); +// for (Object value : values) { +// if (value instanceof MemberValuePair) { +// MemberValuePair pair = (MemberValuePair)value; +// String name = pair.getName().toString(); +// switch (name) { +// case "value": +// case "path": +// mappingPath = getPaths(pair.getValue()); +// break; +// case "method": +// methods = getRequestMethod(pair.getValue()); +// break; +// } +// } +// } +// +// } +// +// if (mappingPath != null) { +// if (Arrays.equals(mappingPath, rm.getSplitPath())) { +// if (methods == null || methods.isEmpty()) { +// return true; +// } else { +// return methods.equals(rm.getRequestMethods()); +// } +// } +// } +// return false; +// } +// +// private static String getExpressionValueAsString(Expression exp) { +// if (exp instanceof StringLiteral) { +// return ((StringLiteral)exp).getLiteralValue(); +// } else if (exp instanceof QualifiedName) { +// return getExpressionValueAsString(((QualifiedName)exp).getName()); +// } else if (exp instanceof SimpleName) { +// return ((SimpleName)exp).getIdentifier(); +// } else { +// return null; +// } +// } +// +// @SuppressWarnings("unchecked") +// private static String[] getPaths(Expression exp) { +// if (exp instanceof ArrayInitializer) { +// ArrayInitializer array = (ArrayInitializer) exp; +// return ((List)array.expressions()).stream() +// .map(e -> getExpressionValueAsString(e)) +// .filter(Objects::nonNull) +// .toArray(String[]::new); +// } else { +// String rm = getExpressionValueAsString(exp); +// if (rm != null) { +// return new String[] { rm }; +// } +// } +// return null; +// } +// +// @SuppressWarnings("unchecked") +// private static Set getRequestMethod(Expression exp) { +// if (exp instanceof ArrayInitializer) { +// ArrayInitializer array = (ArrayInitializer) exp; +// return ((List)array.expressions()).stream() +// .map(e -> getExpressionValueAsString(e)) +// .filter(Objects::nonNull) +// .collect(Collectors.toSet()); +// } else { +// String rm = getExpressionValueAsString(exp); +// if (rm != null) { +// HashSet methods = new HashSet<>(); +// methods.add(rm); +// } +// } +// return null; +// } - @SuppressWarnings("unchecked") - private static String[] getPaths(Expression exp) { - if (exp instanceof ArrayInitializer) { - ArrayInitializer array = (ArrayInitializer) exp; - return ((List)array.expressions()).stream() - .map(e -> getExpressionValueAsString(e)) - .filter(Objects::nonNull) - .toArray(String[]::new); - } else { - String rm = getExpressionValueAsString(exp); - if (rm != null) { - return new String[] { rm }; - } - } - return null; - } - - @SuppressWarnings("unchecked") - private static Set getRequestMethod(Expression exp) { - if (exp instanceof ArrayInitializer) { - ArrayInitializer array = (ArrayInitializer) exp; - return ((List)array.expressions()).stream() - .map(e -> getExpressionValueAsString(e)) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - } else { - String rm = getExpressionValueAsString(exp); - if (rm != null) { - HashSet methods = new HashSet<>(); - methods.add(rm); - } - } - return null; - } - - static class RequestMappingMethod { - - public final SpringBootApp app; - public final RequestMapping requestMapping; - - public RequestMappingMethod(RequestMapping requestMapping, SpringBootApp app) { - this.requestMapping = requestMapping; - this.app = app; - } - } } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java index 92224fc0a..8e0d05537 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/AutowiredHoverProviderTest.java @@ -42,6 +42,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider; import org.springframework.ide.vscode.boot.java.autowired.SpringBootAppProvider; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; @@ -82,7 +83,7 @@ public class AutowiredHoverProviderTest { AutowiredHoverProvider provider = new AutowiredHoverProvider(); String beansJSON = new String(Files.readAllBytes(new File(directory, "runtime-bean-information.json").toPath())); - Range hint = provider.getLiveHoverHint((Annotation)node, document, beansJSON); + Range hint = provider.getLiveHoverHint((Annotation)node, document, LiveBeansModel.parse(beansJSON)); assertNotNull(hint); assertEquals(11, hint.getStart().getLine()); @@ -106,7 +107,7 @@ public class AutowiredHoverProviderTest { ASTNode node = NodeFinder.perform(cu, offset, 0).getParent(); AutowiredHoverProvider provider = new AutowiredHoverProvider(); - Range hint = provider.getLiveHoverHint((Annotation)node, document, (String)null); + Range hint = provider.getLiveHoverHint((Annotation)node, document, LiveBeansModel.parse(null)); assertNull(hint); } @@ -127,7 +128,7 @@ public class AutowiredHoverProviderTest { AutowiredHoverProvider provider = new AutowiredHoverProvider(); String beansJSON = new String(Files.readAllBytes(new File(directory, "wrong-runtime-bean-information.json").toPath())); - Range hint = provider.getLiveHoverHint((Annotation)node, document, beansJSON); + Range hint = provider.getLiveHoverHint((Annotation)node, document, LiveBeansModel.parse(beansJSON)); assertNull(hint); } @@ -146,7 +147,7 @@ public class AutowiredHoverProviderTest { ASTNode node = NodeFinder.perform(cu, offset, 0).getParent(); AutowiredHoverProvider provider = new AutowiredHoverProvider(); - String beansJSON = new String(Files.readAllBytes(new File(directory, "runtime-bean-information.json").toPath())); + LiveBeansModel beansModel = LiveBeansModel.parse(new String(Files.readAllBytes(new File(directory, "runtime-bean-information.json").toPath()))); SpringBootAppProvider bootApp = new SpringBootAppProvider() { @Override @@ -160,8 +161,8 @@ public class AutowiredHoverProviderTest { } @Override - public String getBeans() throws Exception { - return beansJSON; + public LiveBeansModel getBeans() throws Exception { + return beansModel; } }; CompletableFuture hoverFuture = provider.provideHover(null, (Annotation)node, null, offset, document, new SpringBootAppProvider[] {bootApp}); diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ComponentHoverProviderTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ComponentHoverProviderTest.java index 397f0aa2c..2130f9d06 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ComponentHoverProviderTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/ComponentHoverProviderTest.java @@ -42,6 +42,7 @@ import org.junit.Before; import org.junit.Test; import org.springframework.ide.vscode.boot.java.autowired.SpringBootAppProvider; import org.springframework.ide.vscode.boot.java.beans.ComponentHoverProvider; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; @@ -82,7 +83,7 @@ public class ComponentHoverProviderTest { ComponentHoverProvider provider = new ComponentHoverProvider(); String beansJSON = new String(Files.readAllBytes(new File(directory, "runtime-bean-information-automatically-wired.json").toPath())); - Range hint = provider.getLiveHoverHint((Annotation)node, document, beansJSON); + Range hint = provider.getLiveHoverHint((Annotation)node, document, LiveBeansModel.parse(beansJSON)); assertNotNull(hint); assertEquals(10, hint.getStart().getLine()); @@ -108,7 +109,7 @@ public class ComponentHoverProviderTest { ComponentHoverProvider provider = new ComponentHoverProvider(); String beansJSON = new String(Files.readAllBytes(new File(directory, "runtime-bean-information.json").toPath())); - Range hint = provider.getLiveHoverHint((Annotation)node, document, beansJSON); + Range hint = provider.getLiveHoverHint((Annotation)node, document, LiveBeansModel.parse(beansJSON)); assertNull(hint); } @@ -127,7 +128,7 @@ public class ComponentHoverProviderTest { ASTNode node = NodeFinder.perform(cu, offset, 0).getParent(); ComponentHoverProvider provider = new ComponentHoverProvider(); - String beansJSON = new String(Files.readAllBytes(new File(directory, "runtime-bean-information-automatically-wired.json").toPath())); + LiveBeansModel beansModel = LiveBeansModel.parse(new String(Files.readAllBytes(new File(directory, "runtime-bean-information-automatically-wired.json").toPath()))); SpringBootAppProvider bootApp = new SpringBootAppProvider() { @Override @@ -141,8 +142,8 @@ public class ComponentHoverProviderTest { } @Override - public String getBeans() throws Exception { - return beansJSON; + public LiveBeansModel getBeans() throws Exception { + return beansModel; } }; CompletableFuture hoverFuture = provider.provideHover(null, (Annotation) node, null, 0, document, new SpringBootAppProvider[] {bootApp}); diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalsLiveHoverTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalsLiveHoverTest.java index 5eaa0c273..cb887f67c 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalsLiveHoverTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/conditionals/test/ConditionalsLiveHoverTest.java @@ -136,6 +136,66 @@ public class ConditionalsLiveHoverTest { + "Message: @ConditionalOnExpression (#{true}) resulted in true"); } + + @Test + public void testMultipleAppsLiveHover() throws Exception { + + File directory = new File( + ProjectsHarness.class.getResource("/test-projects/test-conditionals-live-hover/").toURI()); + String docUri = "file://" + directory.getAbsolutePath() + + "/src/main/java/example/ConditionalOnMissingBeanConfig.java"; + + // Build a mock running boot app + mockAppProvider.builder().isSpringBootApp(true).port("1000").processId("70000").host("cfapps.io") + .processName("test-conditionals-live-hover") + .getAutoConfigReport( + "{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}") + .build(); + + mockAppProvider.builder().isSpringBootApp(true).port("1001").processId("80000").host("cfapps.io") + .processName("test-conditionals-live-hover") + .getAutoConfigReport( + "{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}") + .build(); + + mockAppProvider.builder().isSpringBootApp(true).port("1002").processId("90000").host("cfapps.io") + .processName("test-conditionals-live-hover") + .getAutoConfigReport( + "{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}") + .build(); + + harness.intialize(directory); + + Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA); + + + editor.assertHoverContains("@ConditionalOnMissingBean", "Condition: OnBeanCondition\n" + "\n" + + "Message: @ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\n" + + "\n" + + "Process ID: 70000\n" + + "\n" + + "Process Name: test-conditionals-live-hover\n" + + "\n" + + "---\n" + + "\n" + + "Condition: OnBeanCondition\n" + "\n" + + "Message: @ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\n" + + "\n" + + "Process ID: 80000\n" + + "\n" + + "Process Name: test-conditionals-live-hover\n" + + "\n" + + "---\n" + + "\n" + + "Condition: OnBeanCondition\n" + "\n" + + "Message: @ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\n" + + "\n" + + "Process ID: 90000\n" + + "\n" + + "Process Name: test-conditionals-live-hover"); + + } + // @Test // public void testMultipleLiveHoverHints() throws Exception { // diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTest.java index 8ab9ea978..dd3455556 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingLiveHoverTest.java @@ -62,7 +62,7 @@ public class RequestMappingLiveHoverTest { harness.intialize(directory); Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA); - editor.assertHoverContains("@RequestMapping(\"/hello-world\")", "Path: [/hello-world](http://cfapps.io:1111/hello-world)\n" + + editor.assertHoverContains("@RequestMapping(\"/hello-world\")", "[http://cfapps.io:1111/hello-world](http://cfapps.io:1111/hello-world)\n" + "\n" + "Process ID: 22022\n" + "\n" + @@ -94,13 +94,13 @@ public class RequestMappingLiveHoverTest { harness.intialize(directory); Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA); - editor.assertHoverContains("@RequestMapping(\"/hello\")", "Path: [/hello](http://cfapps.io:999/hello)\n" + + editor.assertHoverContains("@RequestMapping(\"/hello\")", "[http://cfapps.io:999/hello](http://cfapps.io:999/hello)\n" + "\n" + "Process ID: 76543\n" + "\n" + "Process Name: test-request-mapping-live-hover"); - editor.assertHoverContains("@RequestMapping(\"/goodbye\")", "Path: [/goodbye](http://cfapps.io:999/goodbye)\n" + + editor.assertHoverContains("@RequestMapping(\"/goodbye\")", "[http://cfapps.io:999/goodbye](http://cfapps.io:999/goodbye)\n" + "\n" + "Process ID: 76543\n" + "\n" + @@ -133,7 +133,7 @@ public class RequestMappingLiveHoverTest { } @Test - public void testDeleteMappingHoverHintMethod1() throws Exception { + public void testSimpleMethodHoverHintMethod1() throws Exception { File directory = new File( ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); @@ -171,7 +171,7 @@ public class RequestMappingLiveHoverTest { "}", docUri); - editor.assertHoverContains("@DeleteMapping(\"/greetings\")", "Path: [/greetings](http://cfapps.io:999/greetings)\n" + + editor.assertHoverContains("@DeleteMapping(\"/greetings\")", "[http://cfapps.io:999/greetings](http://cfapps.io:999/greetings)\n" + "\n" + "Process ID: 76543\n" + "\n" + @@ -198,7 +198,7 @@ public class RequestMappingLiveHoverTest { // Ugly, but this is real JSON copied from a real live running app. We want the // mock app to return realistic results if possible .getRequestMappings( - "{\"{[/greetings],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.deleteGreetings()\"}}") + "{\"{[/greetings],methods=[DELETE]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.deleteGreetings()\"}}") . build(); harness.intialize(directory); @@ -213,7 +213,7 @@ public class RequestMappingLiveHoverTest { "public class RestApi {\n" + "\n" + "@DeleteMapping(\"/greetings\")\n" + - "public void deleteGreetings() {\n" + + "public void deleteGreetings(int n) {\n" + "}\n" + "\n" + "}", @@ -223,8 +223,11 @@ public class RequestMappingLiveHoverTest { } + + + @Test - public void testGetMappingHoverHintMethod1() throws Exception { + public void testNoHoverHintMethod() throws Exception { File directory = new File( ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); @@ -241,56 +244,7 @@ public class RequestMappingLiveHoverTest { // Ugly, but this is real JSON copied from a real live running app. We want the // mock app to return realistic results if possible .getRequestMappings( - "{\"{[/greetings],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.greetings()\"}}") - . build(); - - harness.intialize(directory); - - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import java.lang.String;\n" + - "import org.springframework.stereotype.Controller;\n" + - "import org.springframework.web.bind.annotation.GetMapping;\n" + - "\n" + - "@Controller\n" + - "public class RestApi {\n" + - "\n" + - "@GetMapping(\"/greetings\")\n" + - "public String greetings() {\n" + - "return \"Greetings!\";\n" + - "}\n" + - "\n" + - "}", - docUri); - - editor.assertHoverContains("@GetMapping(\"/greetings\")", "Path: [/greetings](http://cfapps.io:999/greetings)\n" + - "\n" + - "Process ID: 76543\n" + - "\n" + - "Process Name: test-request-mapping-live-hover"); - - } - - @Test - public void testGetMappingHoverHintMethod2() throws Exception { - - File directory = new File( - ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); - String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; - - - // Build a mock running boot app - mockAppProvider.builder() - .isSpringBootApp(true) - .port("999") - .processId("76543") - .host("cfapps.io") - .processName("test-request-mapping-live-hover") - // Ugly, but this is real JSON copied from a real live running app. We want the - // mock app to return realistic results if possible - .getRequestMappings( - "{\"{[/greetings],methods=[DELETE]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.greetings()\"}}") + "{\"{[/greetings],methods=[PUT]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.updateGreetings()\"}}") . build(); harness.intialize(directory); @@ -300,194 +254,13 @@ public class RequestMappingLiveHoverTest { "\n" + "import java.lang.String;\n" + "import org.springframework.stereotype.Controller;\n" + - "import org.springframework.web.bind.annotation.GetMapping;\n" + - "\n" + - "@Controller\n" + - "public class RestApi {\n" + - "\n" + - "@GetMapping(\"/greetings\")\n" + - "public String greetings() {\n" + - "return \"Greetings!\";\n" + - "}\n" + - "\n" + - "}", - docUri); - - editor.assertNoHover("@GetMapping(\"/greetings\")"); - - } - - @Test - public void testPostMappingHoverHintMethod1() throws Exception { - - File directory = new File( - ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); - String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; - - - // Build a mock running boot app - mockAppProvider.builder() - .isSpringBootApp(true) - .port("999") - .processId("76543") - .host("cfapps.io") - .processName("test-request-mapping-live-hover") - // Ugly, but this is real JSON copied from a real live running app. We want the - // mock app to return realistic results if possible - .getRequestMappings( - "{\"{[/greetings],methods=[POST]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.createGreetings()\"}}") - . build(); - - harness.intialize(directory); - - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import org.springframework.stereotype.Controller;\n" + - "import org.springframework.web.bind.annotation.PostMapping;\n" + - "\n" + - "@Controller\n" + - "public class RestApi {\n" + - "\n" + - "@PostMapping(\"/greetings\")\n" + - "public void createGreetings() {\n" + - "}\n" + - "\n" + - "}", - docUri); - - editor.assertHoverContains("@PostMapping(\"/greetings\")", "Path: [/greetings](http://cfapps.io:999/greetings)\n" + - "\n" + - "Process ID: 76543\n" + - "\n" + - "Process Name: test-request-mapping-live-hover"); - - } - - @Test - public void testPostMappingHoverHintMethod2() throws Exception { - - File directory = new File( - ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); - String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; - - - // Build a mock running boot app - mockAppProvider.builder() - .isSpringBootApp(true) - .port("999") - .processId("76543") - .host("cfapps.io") - .processName("test-request-mapping-live-hover") - // Ugly, but this is real JSON copied from a real live running app. We want the - // mock app to return realistic results if possible - .getRequestMappings( - "{\"{[/greetings],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.createGreetings()\"}}") - . build(); - - harness.intialize(directory); - - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import org.springframework.stereotype.Controller;\n" + - "import org.springframework.web.bind.annotation.PostMapping;\n" + - "\n" + - "@Controller\n" + - "public class RestApi {\n" + - "\n" + - "@PostMapping(\"/greetings\")\n" + - "public void createGreetings() {\n" + - "}\n" + - "\n" + - "}", - docUri); - - editor.assertNoHover("@PostMapping(\"/greetings\")"); - - } - - @Test - public void testPutMappingHoverHintMethod1() throws Exception { - - File directory = new File( - ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); - String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; - - - // Build a mock running boot app - mockAppProvider.builder() - .isSpringBootApp(true) - .port("999") - .processId("76543") - .host("cfapps.io") - .processName("test-request-mapping-live-hover") - // Ugly, but this is real JSON copied from a real live running app. We want the - // mock app to return realistic results if possible - .getRequestMappings( - "{\"{[/greetings],methods=[PUT]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.updateGreetings()\"}}") - . build(); - - harness.intialize(directory); - - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import org.springframework.stereotype.Controller;\n" + "import org.springframework.web.bind.annotation.PutMapping;\n" + "\n" + "@Controller\n" + "public class RestApi {\n" + "\n" + "@PutMapping(\"/greetings\")\n" + - "public void updateGreetings() {\n" + - "}\n" + - "\n" + - "}", - docUri); - - editor.assertHoverContains("@PutMapping(\"/greetings\")", "Path: [/greetings](http://cfapps.io:999/greetings)\n" + - "\n" + - "Process ID: 76543\n" + - "\n" + - "Process Name: test-request-mapping-live-hover"); - - } - - @Test - public void testPutMappingHoverHintMethod2() throws Exception { - - File directory = new File( - ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); - String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; - - - // Build a mock running boot app - mockAppProvider.builder() - .isSpringBootApp(true) - .port("999") - .processId("76543") - .host("cfapps.io") - .processName("test-request-mapping-live-hover") - // Ugly, but this is real JSON copied from a real live running app. We want the - // mock app to return realistic results if possible - .getRequestMappings( - "{\"{[/greetings],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.updateGreetings()\"}}") - . build(); - - harness.intialize(directory); - - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import org.springframework.stereotype.Controller;\n" + - "import org.springframework.web.bind.annotation.PutMapping;\n" + - "\n" + - "@Controller\n" + - "public class RestApi {\n" + - "\n" + - "@PutMapping(\"/greetings\")\n" + - "public void updateGreetings() {\n" + + "public String updateGreetings(int n) {\n" + "}\n" + "\n" + "}", @@ -497,188 +270,6 @@ public class RequestMappingLiveHoverTest { } - @Test - public void testPatchMappingHoverHintMethod1() throws Exception { - - File directory = new File( - ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); - String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; - - - // Build a mock running boot app - mockAppProvider.builder() - .isSpringBootApp(true) - .port("999") - .processId("76543") - .host("cfapps.io") - .processName("test-request-mapping-live-hover") - // Ugly, but this is real JSON copied from a real live running app. We want the - // mock app to return realistic results if possible - .getRequestMappings( - "{\"{[/greetings],methods=[PATCH]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.patchGreetings()\"}}") - . build(); - - harness.intialize(directory); - - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import org.springframework.stereotype.Controller;\n" + - "import org.springframework.web.bind.annotation.PatchMapping;\n" + - "\n" + - "@Controller\n" + - "public class RestApi {\n" + - "\n" + - "@PatchMapping(\"/greetings\")\n" + - "public void patchGreetings() {\n" + - "}\n" + - "\n" + - "}", - docUri); - - editor.assertHoverContains("@PatchMapping(\"/greetings\")", "Path: [/greetings](http://cfapps.io:999/greetings)\n" + - "\n" + - "Process ID: 76543\n" + - "\n" + - "Process Name: test-request-mapping-live-hover"); - - } - - @Test - public void testPatchMappingHoverHintMethod2() throws Exception { - - File directory = new File( - ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); - String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; - - - // Build a mock running boot app - mockAppProvider.builder() - .isSpringBootApp(true) - .port("999") - .processId("76543") - .host("cfapps.io") - .processName("test-request-mapping-live-hover") - // Ugly, but this is real JSON copied from a real live running app. We want the - // mock app to return realistic results if possible - .getRequestMappings( - "{\"{[/greetings],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.patchGreetings()\"}}") - . build(); - - harness.intialize(directory); - - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import org.springframework.stereotype.Controller;\n" + - "import org.springframework.web.bind.annotation.PatchMapping;\n" + - "\n" + - "@Controller\n" + - "public class RestApi {\n" + - "\n" + - "@PatchMapping(\"/greetings\")\n" + - "public void patchGreetings() {\n" + - "}\n" + - "\n" + - "}", - docUri); - - editor.assertNoHover("@PatchMapping(\"/greetings\")"); - - } - - @Test - public void testMultiRequestMethodMappingHoverHintMethod1() throws Exception { - - File directory = new File( - ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); - String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; - - - // Build a mock running boot app - mockAppProvider.builder() - .isSpringBootApp(true) - .port("999") - .processId("76543") - .host("cfapps.io") - .processName("test-request-mapping-live-hover") - // Ugly, but this is real JSON copied from a real live running app. We want the - // mock app to return realistic results if possible - .getRequestMappings( - "{\"{[/greetings],methods=[POST,PUT]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.updateGreetings()\"}}") - . build(); - - harness.intialize(directory); - - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import org.springframework.stereotype.Controller;\n" + - "import org.springframework.web.bind.annotation.RequestMapping;\n" + - "import org.springframework.web.bind.annotation.RequestMethod.*;\n" + - "\n" + - "@Controller\n" + - "public class RestApi {\n" + - "\n" + - "@RequestMapping(value=\"/greetings\", method={POST, PUT})\n" + - "public void updateGreetings() {\n" + - "}\n" + - "\n" + - "}", - docUri); - - editor.assertHoverContains("@RequestMapping(value=\"/greetings\", method={POST, PUT})", "Path: [/greetings](http://cfapps.io:999/greetings)\n" + - "\n" + - "Process ID: 76543\n" + - "\n" + - "Process Name: test-request-mapping-live-hover"); - - } - - @Test - public void testMultiRequestMethodMappingHoverHintMethod2() throws Exception { - - File directory = new File( - ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); - String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; - - - // Build a mock running boot app - mockAppProvider.builder() - .isSpringBootApp(true) - .port("999") - .processId("76543") - .host("cfapps.io") - .processName("test-request-mapping-live-hover") - // Ugly, but this is real JSON copied from a real live running app. We want the - // mock app to return realistic results if possible - .getRequestMappings( - "{\"{[/greetings],methods=[POST,PUT,PATCH]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.updateGreetings()\"}}") - . build(); - - harness.intialize(directory); - - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import org.springframework.stereotype.Controller;\n" + - "import org.springframework.web.bind.annotation.RequestMapping;\n" + - "import org.springframework.web.bind.annotation.RequestMethod.*;\n" + - "\n" + - "@Controller\n" + - "public class RestApi {\n" + - "\n" + - "@RequestMapping(value=\"/greetings\", method={POST, PUT})\n" + - "public void updateGreetings() {\n" + - "}\n" + - "\n" + - "}", - docUri); - - editor.assertNoHover("@RequestMapping(value=\"/greetings\", method={POST, PUT})"); - - } - @Test public void testMultiPathMappingHoverHintMethod1() throws Exception { @@ -719,8 +310,8 @@ public class RequestMappingLiveHoverTest { "}", docUri); - editor.assertHoverContains("@RequestMapping(value={\"/greetings\", \"/hello\"}, method=GET)", "Path: [/greetings](http://cfapps.io:999/greetings)\n" + - "Path: [/hello](http://cfapps.io:999/hello)\n" + + editor.assertHoverContains("@RequestMapping(value={\"/greetings\", \"/hello\"}, method=GET)", "[http://cfapps.io:999/greetings](http://cfapps.io:999/greetings)\n" + + "[http://cfapps.io:999/hello](http://cfapps.io:999/hello)\n" + "\n" + "Process ID: 76543\n" + "\n" + @@ -728,50 +319,6 @@ public class RequestMappingLiveHoverTest { } - @Test - public void testMultiPathMappingHoverHintMethod2() throws Exception { - - File directory = new File( - ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); - String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; - - - // Build a mock running boot app - mockAppProvider.builder() - .isSpringBootApp(true) - .port("999") - .processId("76543") - .host("cfapps.io") - .processName("test-request-mapping-live-hover") - // Ugly, but this is real JSON copied from a real live running app. We want the - // mock app to return realistic results if possible - .getRequestMappings( - "{\"{[/greetings || /hello || /helloAgain],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.greetings()\"}}") - . build(); - - harness.intialize(directory); - - Editor editor = harness.newEditor(LanguageId.JAVA, - "package com.example;\n" + - "\n" + - "import org.springframework.stereotype.Controller;\n" + - "import org.springframework.web.bind.annotation.RequestMapping;\n" + - "import org.springframework.web.bind.annotation.RequestMethod.*;\n" + - "\n" + - "@Controller\n" + - "public class RestApi {\n" + - "\n" + - "@RequestMapping(value={\"/greetings\", \"/hello\"}, method=GET)\n" + - "public String greetings() {\n" + - "}\n" + - "\n" + - "}", - docUri); - - editor.assertNoHover("@RequestMapping(value={\"/greetings\", \"/hello\"}, method=GET)"); - - } - @Test public void testMethodMatchingHoverHintMethod1() throws Exception { @@ -816,7 +363,7 @@ public class RequestMappingLiveHoverTest { "}", docUri); - editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "Path: [/find](http://cfapps.io:999/find)\n" + + editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" + "\n" + "Process ID: 76543\n" + "\n" + @@ -868,7 +415,7 @@ public class RequestMappingLiveHoverTest { "}", docUri); - editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "Path: [/find](http://cfapps.io:999/find)\n" + + editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" + "\n" + "Process ID: 76543\n" + "\n" + @@ -921,7 +468,7 @@ public class RequestMappingLiveHoverTest { "}", docUri); - editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "Path: [/find](http://cfapps.io:999/find)\n" + + editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" + "\n" + "Process ID: 76543\n" + "\n" + @@ -929,5 +476,280 @@ public class RequestMappingLiveHoverTest { } + @Test + public void testWildCardMethodMatchingHoverHint() throws Exception { + + File directory = new File( + ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); + String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; + + + // Build a mock running boot app + mockAppProvider.builder() + .isSpringBootApp(true) + .port("999") + .processId("76543") + .host("cfapps.io") + .processName("test-request-mapping-live-hover") + // Ugly, but this is real JSON copied from a real live running app. We want the + // mock app to return realistic results if possible + .getRequestMappings( + "{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String,java.util.Map>)\"}}") + . build(); + + harness.intialize(directory); + + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import org.springframework.http.ResponseEntity;\n" + + "import java.lang.String;\n" + + "import java.lang.Integer;\n" + + "import java.util.Map;\n" + + "import org.springframework.stereotype.Controller;\n" + + "import org.springframework.web.bind.annotation.RequestMapping;\n" + + "import org.springframework.web.bind.annotation.RequestMethod.*;\n" + + "\n" + + "@Controller\n" + + "public class RestApi {\n" + + "\n" + + "@RequestMapping(value=\"/find\", method=GET)\n" + + "public Object set(String p1, Map> p2) {\n" + + "return null;\n" + + "}\n" + + "\n" + + "}", + docUri); + + editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" + + "\n" + + "Process ID: 76543\n" + + "\n" + + "Process Name: test-request-mapping-live-hover"); + + } + + @Test + public void testArrayMethodMatchingHoverHint() throws Exception { + + File directory = new File( + ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); + String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; + + + // Build a mock running boot app + mockAppProvider.builder() + .isSpringBootApp(true) + .port("999") + .processId("76543") + .host("cfapps.io") + .processName("test-request-mapping-live-hover") + // Ugly, but this is real JSON copied from a real live running app. We want the + // mock app to return realistic results if possible + .getRequestMappings( + "{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String[])\"}}") + . build(); + + harness.intialize(directory); + + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import org.springframework.http.ResponseEntity;\n" + + "import java.lang.String;\n" + + "import org.springframework.stereotype.Controller;\n" + + "import org.springframework.web.bind.annotation.RequestMapping;\n" + + "import org.springframework.web.bind.annotation.RequestMethod.*;\n" + + "\n" + + "@Controller\n" + + "public class RestApi {\n" + + "\n" + + "@RequestMapping(value=\"/find\", method=GET)\n" + + "public Object set(String[] p) {\n" + + "return null;\n" + + "}\n" + + "\n" + + "}", + docUri); + + editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" + + "\n" + + "Process ID: 76543\n" + + "\n" + + "Process Name: test-request-mapping-live-hover"); + + } + + @Test + public void testMultiDimensionalArrayMethodMatchingHoverHint() throws Exception { + + File directory = new File( + ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); + String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; + + + // Build a mock running boot app + mockAppProvider.builder() + .isSpringBootApp(true) + .port("999") + .processId("76543") + .host("cfapps.io") + .processName("test-request-mapping-live-hover") + // Ugly, but this is real JSON copied from a real live running app. We want the + // mock app to return realistic results if possible + .getRequestMappings( + "{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String[][])\"}}") + . build(); + + harness.intialize(directory); + + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import org.springframework.http.ResponseEntity;\n" + + "import java.lang.String;\n" + + "import org.springframework.stereotype.Controller;\n" + + "import org.springframework.web.bind.annotation.RequestMapping;\n" + + "import org.springframework.web.bind.annotation.RequestMethod.*;\n" + + "\n" + + "@Controller\n" + + "public class RestApi {\n" + + "\n" + + "@RequestMapping(value=\"/find\", method=GET)\n" + + "public Object set(String[][] p) {\n" + + "return null;\n" + + "}\n" + + "\n" + + "}", + docUri); + + editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" + + "\n" + + "Process ID: 76543\n" + + "\n" + + "Process Name: test-request-mapping-live-hover"); + + } + + @Test + public void testVarArgsMethodMatchingHoverHint() throws Exception { + + File directory = new File( + ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); + String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; + + + // Build a mock running boot app + mockAppProvider.builder() + .isSpringBootApp(true) + .port("999") + .processId("76543") + .host("cfapps.io") + .processName("test-request-mapping-live-hover") + // Ugly, but this is real JSON copied from a real live running app. We want the + // mock app to return realistic results if possible + .getRequestMappings( + "{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String...)\"}}") + . build(); + + harness.intialize(directory); + + Editor editor = harness.newEditor(LanguageId.JAVA, + "package com.example;\n" + + "\n" + + "import org.springframework.http.ResponseEntity;\n" + + "import java.lang.String;\n" + + "import org.springframework.stereotype.Controller;\n" + + "import org.springframework.web.bind.annotation.RequestMapping;\n" + + "import org.springframework.web.bind.annotation.RequestMethod.*;\n" + + "\n" + + "@Controller\n" + + "public class RestApi {\n" + + "\n" + + "@RequestMapping(value=\"/find\", method=GET)\n" + + "public Object set(String... p) {\n" + + "return null;\n" + + "}\n" + + "\n" + + "}", + docUri); + + editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "[http://cfapps.io:999/find](http://cfapps.io:999/find)\n" + + "\n" + + "Process ID: 76543\n" + + "\n" + + "Process Name: test-request-mapping-live-hover"); + + } + + @Test + public void testMultipleAppsLiveHover() throws Exception { + + File directory = new File( + ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI()); + String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java"; + + // Build three different instances of the same app running on different ports with different process IDs + mockAppProvider.builder() + .isSpringBootApp(true) + .port("1000") + .processId("70000") + .host("cfapps.io") + .processName("test-request-mapping-live-hover") + // Ugly, but this is real JSON copied from a real live running app. We want the + // mock app to return realistic results if possible + .getRequestMappings( + "{\"/webjars/**\":{\"bean\":\"resourceHandlerMapping\"},\"/**\":{\"bean\":\"resourceHandlerMapping\"},\"/**/favicon.ico\":{\"bean\":\"faviconHandlerMapping\"},\"{[/hello-world],methods=[GET]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public example.Greeting example.HelloWorldController.sayHello(java.lang.String)\"},\"{[/goodbye]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.goodbye()\"},\"{[/hello]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.hello()\"},\"{[/error]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)\"},\"{[/error],produces=[text/html]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)\"}}") + . build(); + + mockAppProvider.builder() + .isSpringBootApp(true) + .port("1001") + .processId("80000") + .host("cfapps.io") + .processName("test-request-mapping-live-hover") + // Ugly, but this is real JSON copied from a real live running app. We want the + // mock app to return realistic results if possible + .getRequestMappings( + "{\"/webjars/**\":{\"bean\":\"resourceHandlerMapping\"},\"/**\":{\"bean\":\"resourceHandlerMapping\"},\"/**/favicon.ico\":{\"bean\":\"faviconHandlerMapping\"},\"{[/hello-world],methods=[GET]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public example.Greeting example.HelloWorldController.sayHello(java.lang.String)\"},\"{[/goodbye]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.goodbye()\"},\"{[/hello]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.hello()\"},\"{[/error]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)\"},\"{[/error],produces=[text/html]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)\"}}") + . build(); + + mockAppProvider.builder() + .isSpringBootApp(true) + .port("1002") + .processId("90000") + .host("cfapps.io") + .processName("test-request-mapping-live-hover") + // Ugly, but this is real JSON copied from a real live running app. We want the + // mock app to return realistic results if possible + .getRequestMappings( + "{\"/webjars/**\":{\"bean\":\"resourceHandlerMapping\"},\"/**\":{\"bean\":\"resourceHandlerMapping\"},\"/**/favicon.ico\":{\"bean\":\"faviconHandlerMapping\"},\"{[/hello-world],methods=[GET]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public example.Greeting example.HelloWorldController.sayHello(java.lang.String)\"},\"{[/goodbye]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.goodbye()\"},\"{[/hello]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public java.lang.String example.RestApi.hello()\"},\"{[/error]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)\"},\"{[/error],produces=[text/html]}\":{\"bean\":\"requestMappingHandlerMapping\",\"method\":\"public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)\"}}") + . build(); + harness.intialize(directory); + + Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA); + editor.assertHoverContains("@RequestMapping(\"/hello\")", "[http://cfapps.io:1000/hello](http://cfapps.io:1000/hello)\n" + + "\n" + + "Process ID: 70000\n" + + "\n" + + "Process Name: test-request-mapping-live-hover\n" + + "\n" + + "---\n" + + "\n" + + "[http://cfapps.io:1001/hello](http://cfapps.io:1001/hello)\n" + + "\n" + + "Process ID: 80000\n" + + "\n" + + "Process Name: test-request-mapping-live-hover\n" + + "\n" + + "---\n" + + "\n" + + "[http://cfapps.io:1002/hello](http://cfapps.io:1002/hello)\n" + + "\n" + + "Process ID: 90000\n" + + "\n" + + "Process Name: test-request-mapping-live-hover"); + + } } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/MockRunningAppProvider.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/MockRunningAppProvider.java index 718e6319f..7710f8c01 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/MockRunningAppProvider.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/project/harness/MockRunningAppProvider.java @@ -20,6 +20,7 @@ import org.mockito.Mockito; import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider; import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp; import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.util.ExceptionUtil; import com.google.common.collect.ImmutableList; @@ -67,7 +68,7 @@ public class MockRunningAppProvider { } public MockAppBuilder beans(String beans) throws Exception { - when(app.getBeans()).thenReturn(beans); + when(app.getBeans()).thenReturn(LiveBeansModel.parse(beans)); return this; } diff --git a/headless-services/commons/commons-boot-app-cli/pom.xml b/headless-services/commons/commons-boot-app-cli/pom.xml index d5c478053..d1ce1a918 100644 --- a/headless-services/commons/commons-boot-app-cli/pom.xml +++ b/headless-services/commons/commons-boot-app-cli/pom.xml @@ -24,6 +24,11 @@ jackson-databind 2.8.8.1 + + commons-io + commons-io + ${commons-io-version} + com.sun diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java index d69e5d1d2..8433882ee 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootApp.java @@ -34,7 +34,9 @@ import org.json.JSONArray; import org.json.JSONObject; import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping; import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMappingImpl1; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.util.Log; +import org.springframework.ide.vscode.commons.util.StringUtil; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableList; @@ -174,7 +176,7 @@ public class SpringBootApp { return null; } - public String getBeans() throws Exception { + private String getBeansJson() throws Exception { Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=beansEndpoint", "Data"); if (result != null) { String beans = new ObjectMapper().writeValueAsString(result); @@ -190,6 +192,14 @@ public class SpringBootApp { return null; } + public LiveBeansModel getBeans() throws Exception { + String json = getBeansJson(); + if (StringUtil.hasText(json)) { + return LiveBeansModel.parse(json); + } + return null; + } + public static Collection parseRequestMappingsJson(String json) { JSONObject obj = new JSONObject(json); Iterator keys = obj.keys(); diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/LiveBean.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/livebean/LiveBean.java similarity index 62% rename from headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/LiveBean.java rename to headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/livebean/LiveBean.java index 6e7b29cde..2b20ad27a 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/autowired/LiveBean.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/livebean/LiveBean.java @@ -8,37 +8,17 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.autowired; +package org.springframework.ide.vscode.commons.boot.app.cli.livebean; import org.json.JSONArray; import org.json.JSONObject; /** * @author Martin Lippert + * @author Kris De Volder */ public class LiveBean { - public static LiveBean parse(JSONObject beansJSON) { - String id = beansJSON.optString("bean"); - String type = beansJSON.optString("type"); - String scope = beansJSON.optString("scope"); - String resource = beansJSON.optString("resource"); - - JSONArray aliasesJSON = beansJSON.getJSONArray("aliases"); - String[] aliases = new String[aliasesJSON.length()]; - for (int i = 0; i < aliasesJSON.length(); i++) { - aliases[i] = aliasesJSON.optString(i); - } - - JSONArray dependenciesJSON = beansJSON.getJSONArray("dependencies"); - String[] dependencies = new String[dependenciesJSON.length()]; - for (int i = 0; i < dependenciesJSON.length(); i++) { - dependencies[i] = dependenciesJSON.optString(i); - } - - return new LiveBean(id, aliases, scope, type, resource, dependencies); - } - private final String id; private final String[] aliases; private final String scope; diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/livebean/LiveBeansModel.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/livebean/LiveBeansModel.java new file mode 100644 index 000000000..5ea7cbf83 --- /dev/null +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/livebean/LiveBeansModel.java @@ -0,0 +1,185 @@ +/******************************************************************************* + * Copyright (c) 2017 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.boot.app.cli.livebean; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Stream; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.ide.vscode.commons.util.Log; +import org.springframework.ide.vscode.commons.util.StringUtil; + +/** + * @author Martin Lippert + */ +public class LiveBeansModel { + + interface Parser { + LiveBeansModel parse(String json) throws Exception; + } + + private static class Boot15Parser implements Parser { + @Override + public LiveBeansModel parse(String json) throws Exception { + LiveBeansModel model = new LiveBeansModel(); + JSONArray mainArray = new JSONArray(json); + for (int i = 0; i < mainArray.length(); i++) { + JSONObject appContext = mainArray.getJSONObject(i); + if (appContext == null) continue; + + JSONArray beansArray = appContext.optJSONArray("beans"); + if (beansArray == null) continue; + + for (int j = 0; j < beansArray.length(); j++) { + JSONObject beanObject = beansArray.getJSONObject(j); + if (beanObject == null) continue; + + LiveBean bean = parseBean(beanObject); + if (bean != null) { + model.add(bean); + } + } + } + return model; + } + private LiveBean parseBean(JSONObject beansJSON) { + String id = beansJSON.optString("bean"); + String type = beansJSON.optString("type"); + String scope = beansJSON.optString("scope"); + String resource = beansJSON.optString("resource"); + + JSONArray aliasesJSON = beansJSON.getJSONArray("aliases"); + String[] aliases = new String[aliasesJSON.length()]; + for (int i = 0; i < aliasesJSON.length(); i++) { + aliases[i] = aliasesJSON.optString(i); + } + + JSONArray dependenciesJSON = beansJSON.getJSONArray("dependencies"); + String[] dependencies = new String[dependenciesJSON.length()]; + for (int i = 0; i < dependenciesJSON.length(); i++) { + dependencies[i] = dependenciesJSON.optString(i); + } + + return new LiveBean(id, aliases, scope, type, resource, dependencies); + } + + } + + private static class Boot20Parser implements Parser { + @Override + public LiveBeansModel parse(String json) throws Exception { + LiveBeansModel model = new LiveBeansModel(); + JSONObject mainObject = new JSONObject(json); + JSONObject beansObject = mainObject.getJSONObject("beans"); + for (String id : beansObject.keySet()) { + JSONObject beanObject = beansObject.getJSONObject(id); + System.out.println(beanObject.toString(3)); + LiveBean bean = parseBean(id, beanObject); + if (bean!=null) { + model.add(bean); + } + } + return model; + } + + private LiveBean parseBean(String id, JSONObject beansJSON) { + String type = beansJSON.optString("type"); + String scope = beansJSON.optString("scope"); + String resource = beansJSON.optString("resource"); + + JSONArray aliasesJSON = beansJSON.getJSONArray("aliases"); + String[] aliases = new String[aliasesJSON.length()]; + for (int i = 0; i < aliasesJSON.length(); i++) { + aliases[i] = aliasesJSON.optString(i); + } + + JSONArray dependenciesJSON = beansJSON.getJSONArray("dependencies"); + String[] dependencies = new String[dependenciesJSON.length()]; + for (int i = 0; i < dependenciesJSON.length(); i++) { + dependencies[i] = dependenciesJSON.optString(i); + } + + return new LiveBean(id, aliases, scope, type, resource, dependencies); + } + } + + private static final Parser[] PARSERS = { + new Boot15Parser(), + new Boot20Parser(), + }; + + public static LiveBeansModel parse(String json) { + List exceptions = new ArrayList<>(PARSERS.length); + if (StringUtil.hasText(json)) { + for (Parser parser : PARSERS) { + try { + LiveBeansModel model = parser.parse(json); + if (model==null) { + throw new NullPointerException("Parser returned a null model (it should not!)"); + } + return model; //good! + } catch (Exception e) { + exceptions.add(e); + } + } + } + //Only getting here if none of the parsers worked... So if at least one parser works, + // we won't log any exceptions. + for (Exception e : exceptions) { + Log.log(e); + } + return new LiveBeansModel(); // allways return at least an empty model. + } + + private final ConcurrentMap> beansViaType; + private final ConcurrentMap> beansViaName; + + protected LiveBeansModel() { + this.beansViaType = new ConcurrentHashMap<>(); + this.beansViaName = new ConcurrentHashMap<>(); + } + + public LiveBean[] getBeansOfType(String fullyQualifiedType) { + List result = beansViaType.get(fullyQualifiedType); + return result != null ? result.toArray(new LiveBean[result.size()]) : new LiveBean[0]; + } + + public LiveBean[] getBeansOfName(String beanName) { + List result = beansViaName.get(beanName); + return result != null ? result.toArray(new LiveBean[result.size()]) : new LiveBean[0]; + } + + protected void add(LiveBean bean) { + String type = bean.getType(); + if (type != null) { + beansViaType.computeIfAbsent(type, (t) -> new ArrayList<>()).add(bean); + } + + String name = bean.getId(); + if (name != null) { + beansViaName.computeIfAbsent(name, (n) -> new ArrayList<>()).add(bean); + } + } + + public Stream getAllBeans() { + return beansViaName.values().stream().flatMap(Collection::stream); + } + + public boolean isEmpty() { + return !getAllBeans().findAny().isPresent(); + } + +} diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/LiveBeansModelTest.java b/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/LiveBeansModelTest.java similarity index 75% rename from headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/LiveBeansModelTest.java rename to headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/LiveBeansModelTest.java index 5c08df184..e7372bd27 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/autowired/test/LiveBeansModelTest.java +++ b/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/LiveBeansModelTest.java @@ -8,15 +8,16 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.autowired.test; +package org.springframework.ide.vscode.commons.boot.app.cli; import static org.junit.Assert.assertEquals; +import java.io.InputStream; + import org.apache.commons.io.IOUtils; import org.junit.Test; -import org.springframework.ide.vscode.boot.java.autowired.LiveBean; -import org.springframework.ide.vscode.boot.java.autowired.LiveBeansModel; -import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; /** * @author Martin Lippert @@ -25,7 +26,7 @@ public class LiveBeansModelTest { @Test public void testSimpleModel() throws Exception { - String json = IOUtils.toString(ProjectsHarness.class.getResourceAsStream("/live-beans-models/simple-live-beans-model.json")); + String json = IOUtils.toString(getResourceAsStream("/live-beans-models/simple-live-beans-model.json")); LiveBeansModel model = LiveBeansModel.parse(json); LiveBean[] bean = model.getBeansOfType("org.test.DependencyA"); @@ -49,7 +50,7 @@ public class LiveBeansModelTest { @Test public void testEmptyModel() throws Exception { - String json = IOUtils.toString(ProjectsHarness.class.getResourceAsStream("/live-beans-models/empty-live-beans-model.json")); + String json = IOUtils.toString(getResourceAsStream("/live-beans-models/empty-live-beans-model.json")); LiveBeansModel model = LiveBeansModel.parse(json); LiveBean[] bean = model.getBeansOfType("org.test.DependencyA"); @@ -58,11 +59,15 @@ public class LiveBeansModelTest { @Test public void testTotallyEmptyModel() throws Exception { - String json = IOUtils.toString(ProjectsHarness.class.getResourceAsStream("/live-beans-models/totally-empty-live-beans-model.json")); + String json = IOUtils.toString(getResourceAsStream("/live-beans-models/totally-empty-live-beans-model.json")); LiveBeansModel model = LiveBeansModel.parse(json); LiveBean[] bean = model.getBeansOfType("org.test.DependencyA"); assertEquals(0, bean.length); } + private InputStream getResourceAsStream(String string) { + return LiveBeansModelTest.class.getResourceAsStream(string); + } + } diff --git a/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java b/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java index 29ef8566e..d48f34cde 100644 --- a/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java +++ b/headless-services/commons/commons-boot-app-cli/src/test/java/org/springframework/ide/vscode/commons/boot/app/cli/SpringBootAppTest.java @@ -12,23 +12,27 @@ package org.springframework.ide.vscode.commons.boot.app.cli; 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.URL; import java.time.Duration; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import org.json.JSONObject; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; +import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel; import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping; import org.springframework.ide.vscode.commons.util.AsyncProcess; +import org.springframework.ide.vscode.commons.util.ExceptionUtil; import org.springframework.ide.vscode.commons.util.ExternalCommand; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.test.ACondition; @@ -37,25 +41,36 @@ import com.google.common.collect.ImmutableList; public class SpringBootAppTest { -// private static final String appName = "actuator-client-15-test-subject"; // Boot 1.5 test app - private static final String appName = "actuator-client-20-test-subject"; //Boot 2.0 test app -// private static final String appName = "actuator-client-20-thin-test-subject"; //Boot 2.0 test app with THIN launcher + private static final String[] appNames = { + "actuator-client-15-test-subject", // Boot 1.5 test app + "actuator-client-20-test-subject", //Boot 2.0 test app + "actuator-client-20-thin-test-subject", // Like the Boot 2.0 app, but packaged with thin launcher instead of fatjar + }; - private static final Duration TIMEOUT = Duration.ofSeconds(30); // in CI build starting the app takes longer than 10s sometimes. - //Output from CI build: Started ActuatorClientTestSubjectApplication in 22.962 seconds (JVM running for 26.028) + private static final Duration TIMEOUT = Duration.ofSeconds(60); // in CI build starting the app takes a while, starting several in parallel takes even longer private static final List TEST_PROFILES = ImmutableList.of("testing", "funny", "cameleon"); - private static AsyncProcess testAppRunner; - private static SpringBootApp testApp; + private static List testAppRunners; @BeforeClass public static void setupClass() throws Exception { - testAppRunner = startTestApplication(SpringBootAppTest.class.getResource("/"+appName+"-0.0.1-SNAPSHOT.jar")); - ACondition.waitFor(TIMEOUT, () -> { - testApp = getAppContaining(appName); - assertNotNull(testApp); - }); + testAppRunners = Arrays.asList(appNames).stream().map(appName -> { + try { + return startTestApplication(SpringBootAppTest.class.getResource("/boot-apps/"+appName+"-0.0.1-SNAPSHOT.jar")); + } catch (Exception e) { + throw ExceptionUtil.unchecked(e); + } + }) + .collect(Collectors.toList()); + } + + @AfterClass + public static void tearDownClass() throws Exception { + for (AsyncProcess process : testAppRunners) { + process.kill(); + } + testAppRunners = null; } private static AsyncProcess startTestApplication(URL jarUrl) throws Exception { @@ -73,95 +88,127 @@ public class SpringBootAppTest { ); } - private static SpringBootApp getAppContaining(String nameFragment) throws Exception { - return SpringBootApp.getAllRunningJavaApps().values().stream().filter(app -> app.getProcessName().contains(nameFragment)).findAny().get(); + private SpringBootApp getAppContaining(String nameFragment) { + try { + return SpringBootApp.getAllRunningJavaApps().values().stream().filter(app -> app.getProcessName().contains(nameFragment)).findAny().get(); + } catch (Exception e) { + throw ExceptionUtil.unchecked(e); + } } - @AfterClass - public static void tearDownClass() throws Exception { - testAppRunner.kill(); + @Ignore @Test public void dumpJvmInfo() throws Exception { + //Ignored because this test may have timing issues. Still useful to + // run locally and inspect dump results, but may need some tweaking. + for (String appName : appNames) { + SpringBootApp testApp = getAppContaining(appName); + testApp.dumpJvmInfo(); + System.out.println("======================================"); + } } - @Test - public void getAllJavaApps() throws Exception { + @Test public void getAllJavaApps() throws Exception { Map allApps = SpringBootApp.getAllRunningJavaApps(); - Optional myProcess = allApps.values().stream().filter(app -> app.getProcessName().contains(appName)).findAny(); - assertTrue(myProcess.isPresent()); - } - - @Test public void dumpJvmInfo() throws Exception { - ACondition.waitFor(TIMEOUT, this::getRequestMappings); - testApp.dumpJvmInfo(); -// SpringBootApp app = getAppContaining("language-server.jar"); -// app.dumpJvmInfo(); + for (String appName : appNames) { + Optional myProcess = allApps.values().stream().filter(app -> app.getProcessName().contains(appName)).findAny(); + assertTrue(appName, myProcess.isPresent()); + } } @Test public void getAllBootApps() throws Exception { Map allApps = SpringBootApp.getAllRunningSpringApps(); - Optional myProcess = allApps.values().stream().filter(app -> app.getProcessName().contains(appName)).findAny(); - assertTrue(myProcess.isPresent()); + for (String appName : appNames) { + Optional myProcess = allApps.values().stream().filter(app -> app.getProcessName().contains(appName)).findAny(); + assertTrue(myProcess.isPresent()); + } } @Test public void getPort() throws Exception { - ACondition.waitFor(TIMEOUT, () -> { - int port = Integer.parseInt(testApp.getPort()); - assertTrue(port > 0); -// System.out.println("port = "+port); - }); + for (SpringBootApp testApp : getTestApps()) { + ACondition.waitFor(TIMEOUT, () -> { + int port = Integer.parseInt(testApp.getPort()); + assertTrue(port > 0); +// System.out.println("port = "+port); + }); + } + } + + private Collection getTestApps() throws Exception { + return ACondition.waitForValue(TIMEOUT, () -> Arrays.asList(appNames).stream() + .map(this::getAppContaining) + .collect(Collectors.toList()) + ); } @Test public void getHost() throws Exception { - ACondition.waitFor(TIMEOUT, () -> { - String host = testApp.getHost(); - assertTrue(StringUtil.hasText(host)); -// System.out.println("host = "+host); - }); + for (SpringBootApp testApp : getTestApps()) { + System.err.println("getHost for "+testApp); + ACondition.waitFor(TIMEOUT, () -> { + String host = testApp.getHost(); + assertTrue(StringUtil.hasText(host)); + System.out.println("host = "+host); + }); + } } @Test public void getEnvironment() throws Exception { - ACondition.waitFor(TIMEOUT, () -> { - String env = testApp.getEnvironment(); - assertNonEmptyJsonObject(env); - System.out.println("env = "+new JSONObject(env).toString(3)); - }); + for (SpringBootApp testApp : getTestApps()) { + ACondition.waitFor(TIMEOUT, () -> { + String env = testApp.getEnvironment(); + assertNonEmptyJsonObject(env); + System.out.println("env = "+new JSONObject(env).toString(3)); + }); + } } @Test public void getBeans() throws Exception { - ACondition.waitFor(TIMEOUT, () -> { - String beans = testApp.getBeans(); - assertNonEmptyJsonObject(beans); -// System.out.println("beans = "+beans); - }); + for (SpringBootApp testApp : getTestApps()) { + try { + ACondition.waitFor(TIMEOUT, () -> { + LiveBeansModel beansModel = testApp.getBeans(); + assertTrue(beansModel.getAllBeans().findAny().isPresent()); + // System.out.println("beans = "+beans); + }); + } catch (Throwable e) { + //Make it easier to identify the culprit of failing test + throw new RuntimeException("Failed for: "+testApp.getProcessName(), e); + } + } } @Test public void getRequestMappings() throws Exception { - ACondition.waitFor(TIMEOUT, () -> { - Collection result = testApp.getRequestMappings(); - assertTrue(result != null && !result.isEmpty()); -// System.out.println("requestMappings = "+result); - }); + for (SpringBootApp testApp : getTestApps()) { + ACondition.waitFor(TIMEOUT, () -> { + Collection result = testApp.getRequestMappings(); + assertTrue(result != null && !result.isEmpty()); +// System.out.println("requestMappings = "+result); + }); + } } @Test public void getAutoConfigReport() throws Exception { - ACondition.waitFor(TIMEOUT, () -> { - String result = testApp.getAutoConfigReport(); - assertNonEmptyJsonObject(result); -// System.out.println("autoconfreport = "+result); - }); + for (SpringBootApp testApp : getTestApps()) { + ACondition.waitFor(TIMEOUT, () -> { + String result = testApp.getAutoConfigReport(); + assertNonEmptyJsonObject(result); + // System.out.println("autoconfreport = "+result); + }); + } } @Test public void getProfiles() throws Exception { - ACondition.waitFor(TIMEOUT, () -> { - List result = testApp.getActiveProfiles(); - assertEquals(ImmutableList.copyOf(TEST_PROFILES), result); - }); + for (SpringBootApp testApp : getTestApps()) { + ACondition.waitFor(TIMEOUT, () -> { + List result = testApp.getActiveProfiles(); + assertEquals(ImmutableList.copyOf(TEST_PROFILES), result); + }); + } } private void assertNonEmptyJsonObject(String jsonData) { diff --git a/headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-15-test-subject-0.0.1-SNAPSHOT.jar b/headless-services/commons/commons-boot-app-cli/src/test/resources/boot-apps/actuator-client-15-test-subject-0.0.1-SNAPSHOT.jar similarity index 100% rename from headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-15-test-subject-0.0.1-SNAPSHOT.jar rename to headless-services/commons/commons-boot-app-cli/src/test/resources/boot-apps/actuator-client-15-test-subject-0.0.1-SNAPSHOT.jar diff --git a/headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-20-test-subject-0.0.1-SNAPSHOT.jar b/headless-services/commons/commons-boot-app-cli/src/test/resources/boot-apps/actuator-client-20-test-subject-0.0.1-SNAPSHOT.jar similarity index 100% rename from headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-20-test-subject-0.0.1-SNAPSHOT.jar rename to headless-services/commons/commons-boot-app-cli/src/test/resources/boot-apps/actuator-client-20-test-subject-0.0.1-SNAPSHOT.jar diff --git a/headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-20-thin-test-subject-0.0.1-SNAPSHOT.jar b/headless-services/commons/commons-boot-app-cli/src/test/resources/boot-apps/actuator-client-20-thin-test-subject-0.0.1-SNAPSHOT.jar similarity index 100% rename from headless-services/commons/commons-boot-app-cli/src/test/resources/actuator-client-20-thin-test-subject-0.0.1-SNAPSHOT.jar rename to headless-services/commons/commons-boot-app-cli/src/test/resources/boot-apps/actuator-client-20-thin-test-subject-0.0.1-SNAPSHOT.jar diff --git a/headless-services/boot-java-language-server/src/test/resources/live-beans-models/empty-live-beans-model.json b/headless-services/commons/commons-boot-app-cli/src/test/resources/live-beans-models/empty-live-beans-model.json similarity index 100% rename from headless-services/boot-java-language-server/src/test/resources/live-beans-models/empty-live-beans-model.json rename to headless-services/commons/commons-boot-app-cli/src/test/resources/live-beans-models/empty-live-beans-model.json diff --git a/headless-services/boot-java-language-server/src/test/resources/live-beans-models/simple-live-beans-model.json b/headless-services/commons/commons-boot-app-cli/src/test/resources/live-beans-models/simple-live-beans-model.json similarity index 100% rename from headless-services/boot-java-language-server/src/test/resources/live-beans-models/simple-live-beans-model.json rename to headless-services/commons/commons-boot-app-cli/src/test/resources/live-beans-models/simple-live-beans-model.json diff --git a/headless-services/boot-java-language-server/src/test/resources/live-beans-models/totally-empty-live-beans-model.json b/headless-services/commons/commons-boot-app-cli/src/test/resources/live-beans-models/totally-empty-live-beans-model.json similarity index 100% rename from headless-services/boot-java-language-server/src/test/resources/live-beans-models/totally-empty-live-beans-model.json rename to headless-services/commons/commons-boot-app-cli/src/test/resources/live-beans-models/totally-empty-live-beans-model.json diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JLRMethodParser.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JLRMethodParser.java index 970480e8c..b151b37d9 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JLRMethodParser.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/parser/JLRMethodParser.java @@ -108,15 +108,24 @@ public class JLRMethodParser { } break; default: - if (Character.isJavaIdentifierPart(ch)) { - currentParameter.append(ch); - } + currentParameter.append(ch); } } if (currentParameter.length() > 0) { parameters.add(currentParameter.toString()); } - return parameters.toArray(new String[parameters.size()]); + return parameters.stream() + .map(p -> { + int genericStart = p.indexOf('<'); + int genericEnd = p.lastIndexOf('>'); + if (genericStart < genericEnd) { + return p.substring(0, genericStart) + p.substring(genericEnd + 1); + } else { + return p; + } + }) + .map(p -> p.replaceAll("\\.\\.\\.", "[]")) + .toArray(String[]::new); } @Override diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/CompositeJavaProjectFinder.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/CompositeJavaProjectFinder.java index b32937259..fbeb5921a 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/CompositeJavaProjectFinder.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/languageserver/java/CompositeJavaProjectFinder.java @@ -14,14 +14,13 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.Optional; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.springframework.ide.vscode.commons.java.IJavaProject; /** - * Composite project manager that acts a single project manager but consissts of many project managers + * Composite project manager that acts a single project manager but consists of many project managers * * @author Alex Boyko * @@ -48,7 +47,7 @@ public class CompositeJavaProjectFinder implements JavaProjectFinder { @Override public Optional find(TextDocumentIdentifier doc) { - return projectFinders.stream().map(finder -> finder.find(doc)).filter(Optional::isPresent).findFirst().orElse(null); + return projectFinders.stream().map(finder -> finder.find(doc)).filter(Optional::isPresent).findFirst().orElseGet(() -> Optional.empty()); } } diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/test/ACondition.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/test/ACondition.java index 034447d17..2d6599557 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/test/ACondition.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/test/ACondition.java @@ -11,6 +11,8 @@ package org.springframework.ide.vscode.commons.util.test; import java.time.Duration; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; import org.springframework.ide.vscode.commons.util.ExceptionUtil; @@ -44,5 +46,14 @@ public class ACondition { } while (System.currentTimeMillis()-startTime < timeout_millis); throw ExceptionUtil.exception(lastException); } + + /** + * Retries fecthing a value until it succeeds without an error, or until timeout exceeded. + */ + public static T waitForValue(Duration timeout, Callable provider) throws Exception { + AtomicReference result = new AtomicReference<>(null); + waitFor(timeout, () -> result.set(provider.call())); + return result.get(); + } } diff --git a/headless-services/commons/pom.xml b/headless-services/commons/pom.xml index 51fa1730a..55e8c8007 100644 --- a/headless-services/commons/pom.xml +++ b/headless-services/commons/pom.xml @@ -85,6 +85,7 @@ 3.0.5.RELEASE 0.6.0.RELEASE 2.4.0.RELEASE + 2.4