diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertyInfo.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertyInfo.java index 2ab911d7c..f0a64da2a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertyInfo.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertyInfo.java @@ -145,7 +145,7 @@ public class PropertyInfo { public HintProvider getHints(TypeUtil typeUtil) { Type type = TypeParser.parse(this.type); - if (TypeUtil.isMap(type)) { + if (typeUtil.isMap(type)) { return HintProviders.forMap(keyHints(typeUtil), valueHints(typeUtil), TypeUtil.getDomainType(type)); } else if (TypeUtil.isSequencable(type)) { return HintProviders.forAllValueContexts(valueHints(typeUtil)); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypeUtil.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypeUtil.java index 9bf3614e1..0fc570aa5 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypeUtil.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypeUtil.java @@ -485,7 +485,7 @@ public class TypeUtil { return type!=null && type.getErasure().endsWith("[]"); } - public static boolean isMap(Type type) { + public boolean isMap(Type type) { //Note: to be really correct we should use JDT infrastructure to resolve //type in project classpath instead of using Java reflection. //However, use reflection here is okay assuming types we care about @@ -493,9 +493,14 @@ public class TypeUtil { //also potentialy be very slow. if (type!=null) { String erasure = type.getErasure(); + if ("java.util.Map".equals(erasure)) { + //quick / easy case. No looking for types and hierarchies required. + return true; + } try { - Class erasureClass = Class.forName(erasure); - return Map.class.isAssignableFrom(erasureClass); + IType mapType = findType("java.util.Map"); + IType erasureType = findType(erasure); + return isAssignableFrom(mapType, erasureType); } catch (Exception e) { //type not resolveable } @@ -503,6 +508,36 @@ public class TypeUtil { return false; } + private boolean isAssignableFrom(IType mapType, IType erasureType) { + Set seen = new HashSet<>(); + return searchSuperTypes(seen, erasureType, mapType.getFullyQualifiedName()); + } + + + private boolean searchSuperTypes(Set seen, IType searchIn, String fqTargetType) { + if (searchIn!=null) { + String fqName = searchIn.getFullyQualifiedName(); + if (fqName.equals(fqTargetType)) { + return true; + } + if (seen.add(fqName)) { + for (String itfName : searchIn.getSuperInterfaceNames()) { + IType itf = findType(itfName); + if (searchSuperTypes(seen, itf, fqTargetType)) { + return true; + } + } + String klassName = searchIn.getSuperclassName(); + IType klass = findType(klassName); + if (searchSuperTypes(seen, klass, fqTargetType)) { + return true; + } + } + } + return false; + } + + /** * Get domain type for a map or list generic type. */ @@ -966,9 +1001,9 @@ public class TypeUtil { * List>> -> 2 * Map<*,List> -> 2 */ - public static int getDimensionality(Type type) { + public int getDimensionality(Type type) { int dim = 0; - while (isSequencable(type) || isMap(type)) { + while (isSequencable(type) || this.isMap(type)) { dim++; type = getDomainType(type); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/PropertyNavigator.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/PropertyNavigator.java index ceaea9e6d..2fc448410 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/PropertyNavigator.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/reconcile/PropertyNavigator.java @@ -163,7 +163,7 @@ public class PropertyNavigator { * checked to be 'dotable'. */ private Type dotNavigate(int offset, Type type) { - if (TypeUtil.isMap(type)) { + if (typeUtil.isMap(type)) { int keyStart = offset+1; Type domainType = TypeUtil.getDomainType(type); int keyEnd = -1; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java index 05d020d69..a1c8c953c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java @@ -116,7 +116,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon protected String appendTextFor(Type type) { //Note that proper indentation after each \n" is added automatically //so the strings created here do not need to contain indentation spaces - if (TypeUtil.isMap(type)) { + if (typeUtil.isMap(type)) { //ready to enter nested map key on next line return "\n"+YamlIndentUtil.INDENT_STR; } if (TypeUtil.isSequencable(type)) { @@ -328,7 +328,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon @Override public YamlAssistContext traverse(YamlPathSegment s) { if (s.getType()==YamlPathSegmentType.VAL_AT_KEY) { - if (TypeUtil.isSequencable(type) || TypeUtil.isMap(type)) { + if (TypeUtil.isSequencable(type) || typeUtil.isMap(type)) { return contextWith(s, TypeUtil.getDomainType(type)); } String key = s.toPropString(); @@ -632,7 +632,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon private static List getAllJavaElements(TypeUtil typeUtil, Type parentType, String propName) { if (propName!=null) { Type beanType = parentType; - if (TypeUtil.isMap(beanType)) { + if (typeUtil.isMap(beanType)) { Type keyType = typeUtil.getKeyType(beanType); if (keyType!=null && typeUtil.isEnum(keyType)) { IField field = typeUtil.getEnumConstant(keyType, propName); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java index 8b7c48250..fc1467aa9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/reconcile/ApplicationYamlASTReconciler.java @@ -224,7 +224,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { checkForDuplicateKeys(mapping); if (typeUtil.isAtomic(type)) { expectTypeFoundMapping(type, mapping); - } else if (TypeUtil.isMap(type) || TypeUtil.isSequencable(type)) { + } else if (typeUtil.isMap(type) || TypeUtil.isSequencable(type)) { Type keyType = typeUtil.getKeyType(type); Type valueType = TypeUtil.getDomainType(type); if (keyType!=null) {