PT 152801833 - Added support for negative matches parsing for live

conditionals
This commit is contained in:
nsingh
2017-11-14 22:57:41 -08:00
parent 147f2548ae
commit 539afeca3a
7 changed files with 239 additions and 61 deletions

View File

@@ -140,18 +140,18 @@ public class ConditionalsLiveHoverProvider implements HoverProvider {
// Check that Java type in annotation in editor matches Java information in the live Conditional
ASTNode parent = annotation.getParent();
String rawJsonKey = liveConditional.getPositiveMatchKey();
String typeInfo = liveConditional.getTypeInfo();
if (parent instanceof MethodDeclaration) {
MethodDeclaration methodDec = (MethodDeclaration) parent;
IMethodBinding binding = methodDec.resolveBinding();
String annotationDeclaringClassName = binding.getDeclaringClass().getName();
String annotationMethodName = binding.getName();
return rawJsonKey.contains(annotationDeclaringClassName) && rawJsonKey.contains(annotationMethodName);
return typeInfo.contains(annotationDeclaringClassName) && typeInfo.contains(annotationMethodName);
} else if (parent instanceof TypeDeclaration) {
TypeDeclaration typeDec = (TypeDeclaration) parent;
String annotationDeclaringClassName = typeDec.resolveBinding().getName();
return rawJsonKey.contains(annotationDeclaringClassName);
return typeInfo.contains(annotationDeclaringClassName);
}
return false;
}

View File

@@ -69,7 +69,7 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.conditionalsJson(
"{\"positiveMatches\":{\"ConditionalOnBeanConfig#hi\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'\"}]}}")
.build();
@@ -93,7 +93,7 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.conditionalsJson(
"{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}")
.build();
@@ -116,7 +116,7 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.conditionalsJson(
"{\"positiveMatches\":{\"HelloConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}],\"HelloConfig2#hi\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'\"}],\"MultipleConditionals#hi\":[{\"condition\":\"OnClassCondition\",\"message\":\"@ConditionalOnClass found required class; @ConditionalOnMissingClass did not find unwanted class\"},{\"condition\":\"OnWebApplicationCondition\",\"message\":\"@ConditionalOnWebApplication (required) found StandardServletEnvironment\"},{\"condition\":\"OnJavaCondition\",\"message\":\"@ConditionalOnJava (1.8 or newer) found 1.8\"},{\"condition\":\"OnExpressionCondition\",\"message\":\"@ConditionalOnExpression (#{true}) resulted in true\"},{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found beans 'hi', 'missing'\"}]}}")
.build();
@@ -158,19 +158,19 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1000").processId("70000").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.conditionalsJson(
"{\"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")
.positiveMatchesJsonForLiveConditionals(
.conditionalsJson(
"{\"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")
.positiveMatchesJsonForLiveConditionals(
.conditionalsJson(
"{\"positiveMatches\":{\"ConditionalOnMissingBeanConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}]}}")
.build();
@@ -218,7 +218,7 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1000").processId("70000").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.conditionalsJson(
"{\"positiveMatches\":{\"HelloConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}],\"HelloConfig2#hi\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'\"}],\"MultipleConditionals#hi\":[{\"condition\":\"OnClassCondition\",\"message\":\"@ConditionalOnClass found required class; @ConditionalOnMissingClass did not find unwanted class\"},{\"condition\":\"OnWebApplicationCondition\",\"message\":\"@ConditionalOnWebApplication (required) found StandardServletEnvironment\"},{\"condition\":\"OnJavaCondition\",\"message\":\"@ConditionalOnJava (1.8 or newer) found 1.8\"},{\"condition\":\"OnExpressionCondition\",\"message\":\"@ConditionalOnExpression (#{true}) resulted in true\"},{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found beans 'hi', 'missing'\"}]}}")
.build();
@@ -264,7 +264,7 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1000").processId("70000").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.conditionalsJson(
"{\"positiveMatches\":{\"MultipleConditionalsPT152535713#hi\":[{\"condition\":\"OnWebApplicationCondition\",\"message\":\"@ConditionalOnWebApplication (required) found StandardServletEnvironment\"},{\"condition\":\"OnJavaCondition\",\"message\":\"@ConditionalOnJava (1.8 or newer) found 1.8\"}]}}")
.build();
@@ -290,7 +290,7 @@ public class ConditionalsLiveHoverTest {
}
@Test
public void testHighlights() throws Exception {
public void testHighlightsMethodConditionals() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-conditionals-live-hover/").toURI());
@@ -299,18 +299,131 @@ public class ConditionalsLiveHoverTest {
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io")
.processName("test-conditionals-live-hover")
.positiveMatchesJsonForLiveConditionals(
.conditionalsJson(
"{\"positiveMatches\":{\"HelloConfig#missing\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnMissingBean (types: example.Hello; SearchStrategy: all) did not find any beans\"}],\"HelloConfig2#hi\":[{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found bean 'missing'\"}],\"MultipleConditionals#hi\":[{\"condition\":\"OnClassCondition\",\"message\":\"@ConditionalOnClass found required class; @ConditionalOnMissingClass did not find unwanted class\"},{\"condition\":\"OnWebApplicationCondition\",\"message\":\"@ConditionalOnWebApplication (required) found StandardServletEnvironment\"},{\"condition\":\"OnJavaCondition\",\"message\":\"@ConditionalOnJava (1.8 or newer) found 1.8\"},{\"condition\":\"OnExpressionCondition\",\"message\":\"@ConditionalOnExpression (#{true}) resulted in true\"},{\"condition\":\"OnBeanCondition\",\"message\":\"@ConditionalOnBean (types: example.Hello; SearchStrategy: all) found beans 'hi', 'missing'\"}]}}")
.build();
harness.intialize(directory);
String content = "package example;\n" +
"\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnJava;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnNotWebApplication;\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;\n" +
"import org.springframework.context.annotation.Bean;\n" +
"import org.springframework.context.annotation.Configuration;\n" +
"\n" +
"@Configuration\n" +
"public class MultipleConditionals {\n" +
"\n" +
" @Bean\n" +
" @ConditionalOnBean\n" +
" @ConditionalOnWebApplication\n" +
" @ConditionalOnJava(value=ConditionalOnJava.JavaVersion.EIGHT)\n" +
" @ConditionalOnMissingClass\n" +
" @ConditionalOnExpression\n" +
" public Hello hi() {\n" +
" return null;\n" +
" }\n" +
"}";
Editor editor = harness.newEditorFromFileUri(docUri, LanguageId.JAVA);
Editor editor = harness.newEditor( LanguageId.JAVA, content, docUri);
editor.assertHighlights("@ConditionalOnBean", "@ConditionalOnWebApplication",
"@ConditionalOnJava(value=ConditionalOnJava.JavaVersion.EIGHT)", "@ConditionalOnMissingClass",
"@ConditionalOnExpression");
}
@Test
public void testHighlightsTypeConditionals() 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/MultipleConditionals.java";
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("22022").host("cfapps.io")
.processName("test-conditionals-live-hover")
.conditionalsJson(
"{\"negativeMatches\": {\n" +
" \"MyConditionalComponent\": {\n" +
" \"notMatched\": [\n" +
" {\n" +
" \"condition\": \"OnClassCondition\",\n" +
" \"message\": \"@ConditionalOnClass did not find required class 'java.lang.String2'\"\n" +
" }\n" +
" ],\n" +
" \"matched\": []\n" +
" }\n"
+ "}\n"
+ "}")
.build();
harness.intialize(directory);
String content = "package com.example;\n" +
"\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\n" +
"import org.springframework.stereotype.Component;\n" +
"\n" +
"@Component\n" +
"@ConditionalOnClass(name=\"java.lang.String2\")\n" +
"public class MyConditionalComponent {\n" +
"}";
Editor editor = harness.newEditor( LanguageId.JAVA, content, docUri);
editor.assertHighlights("@ConditionalOnClass(name=\"java.lang.String2\")");
}
@Test
public void testNegativeMatches() 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/MultipleConditionals.java";
// Build a mock running boot app
mockAppProvider.builder().isSpringBootApp(true).port("1111").processId("67950").host("cfapps.io")
.processName("test-conditionals-live-hover")
.conditionalsJson(
"{\"negativeMatches\": {\n" +
" \"MyConditionalComponent\": {\n" +
" \"notMatched\": [\n" +
" {\n" +
" \"condition\": \"OnClassCondition\",\n" +
" \"message\": \"@ConditionalOnClass did not find required class 'java.lang.String2'\"\n" +
" }\n" +
" ],\n" +
" \"matched\": []\n" +
" }\n"
+ "}\n"
+ "}")
.build();
harness.intialize(directory);
String content = "package com.example;\n" +
"\n" +
"import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\n" +
"import org.springframework.stereotype.Component;\n" +
"\n" +
"@Component\n" +
"@ConditionalOnClass(name=\"java.lang.String2\")\n" +
"public class MyConditionalComponent {\n" +
"}";
Editor editor = harness.newEditor( LanguageId.JAVA, content, docUri);
editor.assertHoverContains("@ConditionalOnClass(name=\"java.lang.String2\")", "@ConditionalOnClass did not find required class 'java.lang.String2'\n" +
"\n" +
"Process [PID=67950, name=`test-conditionals-live-hover`]");
}
}

View File

@@ -116,7 +116,7 @@ public class MockRunningAppProvider {
return this;
}
public MockAppBuilder positiveMatchesJsonForLiveConditionals(String rawJson) throws Exception{
public MockAppBuilder conditionalsJson(String rawJson) throws Exception{
when(app.getLiveConditionals()).thenReturn(SpringBootApp.getLiveConditionals(rawJson, processId, processName));
return this;
}

View File

@@ -22,11 +22,4 @@ public class MultipleConditionals {
public Hello hi() {
return null;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnNotWebApplication
public Hello missing() {
return null;
}
}

View File

@@ -16,7 +16,7 @@ public class LiveConditional {
private String message;
private String processId;
private String processName;
private String positiveMatchKey;
private String typeInfo;
public LiveConditional() {
@@ -38,8 +38,8 @@ public class LiveConditional {
return processName;
}
public String getPositiveMatchKey() {
return positiveMatchKey;
public String getTypeInfo() {
return typeInfo;
}
public static class LiveConditionalBuilder {
@@ -67,13 +67,31 @@ public class LiveConditional {
}
/**
* This is a JSON key in "positiveMatches" element in the autoconfig report that contains information regarding
* the method that the conditional is applied to.
* @param positiveMatchKey
* Type information for which a conditional is applied to.
* <p/>
*
* Example:
* <p/>
* For this class:
* <p/>
* "@ConditionalOnClass(name="java.lang.String2")
* public class MyConditionalComponent {
* }"
* <p/>
* This is the "real" autoconfig JSON:
* <p/>
* "negativeMatches": { "MyConditionalComponent": { "notMatched": [ {
* "condition": "OnClassCondition", "message": "@ConditionalOnClass did not find
* required class 'java.lang.String2'" } ], "matched": [] }
* <p/>
* In this example, "MyConditionalComponent" information in the JSON indicates the type where the conditional is being applied to.
* <p/>
* Type info can also contain method information if a conditional annotation is applied to a method. Example: MyConditionalComponent#myBean)
* @param typeInfo
* @return
*/
public LiveConditionalBuilder positiveMatchKey(String positiveMatchKey) {
conditional.positiveMatchKey = positiveMatchKey;
public LiveConditionalBuilder typeInfo(String typeInfo) {
conditional.typeInfo = typeInfo;
return this;
}

View File

@@ -50,6 +50,8 @@ public class LiveConditionalParser {
if (StringUtil.hasText(autoConfigRecord)) {
getConditionalsFromPositiveMatches(autoConfigRecord).stream()
.forEach(conditional -> allConditionals.add(conditional));
getConditionalsFromNegativeMatches(autoConfigRecord).stream()
.forEach(conditional -> allConditionals.add(conditional));
}
if (!allConditionals.isEmpty()) {
return Optional.of(allConditionals);
@@ -61,9 +63,7 @@ public class LiveConditionalParser {
}
/**
* Fetches the "positiveMatches" element in the autoconfig report that contains conditional information.
* @param positiveMatchKey
* @return
* Fetches the "positiveMatches" element in the autoconfig report JSON that contains conditional information.
*/
private Optional<JSONObject> getPositiveMatchesJson(String autoConfigReport) {
JSONObject autoConfigJson = new JSONObject(autoConfigReport);
@@ -83,46 +83,100 @@ public class LiveConditionalParser {
return Optional.empty();
}
/**
* Fetches the "negativeMatches" element in the autoconfig report JSON that contains conditional information.
*/
private Optional<JSONObject> getNegativeMatchesJson(String autoConfigReport) {
JSONObject autoConfigJson = new JSONObject(autoConfigReport);
Iterator<String> keys = autoConfigJson.keys();
while (keys.hasNext()) {
String key = keys.next();
if ("negativeMatches".equals(key)) {
Object obj = autoConfigJson.get(key);
if (obj instanceof JSONObject) {
return Optional.of((JSONObject) obj);
}
}
}
return Optional.empty();
}
/**
* Fetches all the conditionals listed in the the "positiveMatches" element in the autoconfig report.
*
*/
private List<LiveConditional> getConditionalsFromPositiveMatches(String autoconfigReport) {
private List<LiveConditional> getConditionalsFromPositiveMatches(String autoConfigReport) {
List<LiveConditional> conditions = new ArrayList<>();
getPositiveMatchesJson(autoconfigReport).ifPresent((positiveMatches) -> {
Iterator<String> pMKeys = positiveMatches.keys();
while (pMKeys.hasNext()) {
getPositiveMatchesJson(autoConfigReport).ifPresent((matches) -> {
matches.keySet().stream().forEach(typeInfo -> {
// The positive match key contains the bean method information where conditional
// was applied to
String positiveMatchKey = pMKeys.next();
JSONArray matchList = (JSONArray) positiveMatches.get(positiveMatchKey);
matchList.forEach((match) -> {
if (match instanceof JSONObject) {
resolveConditional(positiveMatchKey, (JSONObject) match)
.ifPresent((condition) -> conditions.add(condition));
}
});
}
});
Object val = matches.get(typeInfo);
if (val instanceof JSONArray) {
JSONArray contentList = (JSONArray) val;
parseConditionalsFromContentList(conditions, typeInfo, contentList);
}
});
});
return conditions;
}
private Optional<LiveConditional> resolveConditional(String positiveMatchKey, JSONObject conditionalJson) {
if (conditionalJson != null) {
String condition = (String) conditionalJson.get("condition");
String message = (String) conditionalJson.get("message");
// We care about the message itself as it contains the actual annotation as well
// as the reason it matched
if (StringUtil.hasText(message)) {
return Optional.of(LiveConditional.builder().processId(appProcessId).processName(appProcessName)
.condition(condition).message(message).positiveMatchKey(positiveMatchKey).build());
}
}
return Optional.empty();
private List<LiveConditional> getConditionalsFromNegativeMatches(String autoConfigReport) {
List<LiveConditional> conditions = new ArrayList<>();
// The JSON structure being parsed is:
// "negativeMatches": {
// "MyConditionalComponent": {
// "notMatched": [
// {
// "condition": "OnClassCondition",
// "message": "@ConditionalOnClass did not find required class 'java.lang.String2'"
// }
// ],
// "matched": []
// }
getNegativeMatchesJson(autoConfigReport).ifPresent((matches) -> {
// The key in the "matches" JSON contains the live type information where the conditional was applied to
matches.keySet().stream().forEach(typeInfo -> {
// The positive match key contains the bean method information where conditional
// was applied to
Object val = matches.get(typeInfo);
if (val instanceof JSONObject) {
JSONObject negativeMatches = (JSONObject) val;
negativeMatches.keySet().stream().forEach((key) -> {
JSONArray contentList = (JSONArray) negativeMatches.get(key);
parseConditionalsFromContentList(conditions, typeInfo, contentList);
});
}
});
});
return conditions;
}
private void parseConditionalsFromContentList(List<LiveConditional> conditionals, String typeInfo,
JSONArray contentList) {
contentList.forEach((content) -> {
if (content instanceof JSONObject) {
JSONObject conditionalJson = (JSONObject) content;
String condition = (String) conditionalJson.get("condition");
String message = (String) conditionalJson.get("message");
// We care about the message itself as it contains the actual annotation as well
// as the reason it matched
if (StringUtil.hasText(message)) {
LiveConditional conditional = LiveConditional.builder().processId(appProcessId)
.processName(appProcessName).condition(condition).message(message).typeInfo(typeInfo)
.build();
conditionals.add(conditional);
}
}
});
}
public static Optional<List<LiveConditional>> parse(String autoConfigRecord, String appProcessId,
String appProcessName) {
return new LiveConditionalParser(autoConfigRecord, appProcessId, appProcessName).parse();

View File

@@ -251,7 +251,7 @@ public class SpringBootApp {
*/
public static Optional<List<LiveConditional>> getLiveConditionals(String autoConfigReport, String processId,
String processName) {
return new LiveConditionalParser(autoConfigReport, processId, processName).parse();
return LiveConditionalParser.parse(autoConfigReport, processId, processName);
}
public String getAutoConfigReport() throws Exception {