diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/CommonLanguageTools.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/CommonLanguageTools.java index 9217745c0..efc8720b8 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/CommonLanguageTools.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/common/CommonLanguageTools.java @@ -70,7 +70,7 @@ public class CommonLanguageTools { public static Collection getValueHints(FuzzyMap index, TypeUtil typeUtil, String query, String propertyName, EnumCaseMode caseMode) { Type type = getValueType(index, typeUtil, propertyName); - if (TypeUtil.isSequencable(type)) { + if (typeUtil.isSequencable(type)) { //It is useful to provide content assist for the values in the list when entering a list type = TypeUtil.getDomainType(type); } 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 f0a64da2a..ef72d0db3 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 @@ -147,7 +147,7 @@ public class PropertyInfo { Type type = TypeParser.parse(this.type); if (typeUtil.isMap(type)) { return HintProviders.forMap(keyHints(typeUtil), valueHints(typeUtil), TypeUtil.getDomainType(type)); - } else if (TypeUtil.isSequencable(type)) { + } else if (typeUtil.isSequencable(type)) { return HintProviders.forAllValueContexts(valueHints(typeUtil)); } else { return HintProviders.forHere(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 0fc570aa5..00e3e84f6 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 @@ -99,6 +99,9 @@ public class TypeUtil { private static final Object OBJECT_TYPE_NAME = Object.class.getName(); private static final String STRING_TYPE_NAME = String.class.getName(); + private static final String MAP_TYPE_NAME = Map.class.getName(); + private static final String SET_TYPE_NAME = Set.class.getName(); + private static final String LIST_TYPE_NAME = List.class.getName(); private static final String INET_ADDRESS_TYPE_NAME = InetAddress.class.getName(); private static final String DURATION_TYPE_NAME = Duration.class.getName(); private static final String CLASS_TYPE_NAME = Class.class.getName(); @@ -443,7 +446,7 @@ public class TypeUtil { * use the notation []= in property file * for properties of this type. */ - public static boolean isBracketable(Type type) { + public boolean isBracketable(Type type) { //Note array types where once not considered 'Bracketable' //see: STS-4031 @@ -452,32 +455,13 @@ public class TypeUtil { //This is actually more logical too. //So '[' notation in props file can be used for either list or arrays (at least in recent versions of boot). //Note also 'Set' are now considered bracketable. See: https://www.pivotaltracker.com/story/show/154644992 - return isArray(type) || isCollection(List.class, type) || isCollection(Set.class, type); - } - - @SuppressWarnings("rawtypes") - private static boolean isCollection( Class klass, 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 - //are part of JRE standard libraries. Using eclipse 'type hirearchy' would - //also potentialy be very slow. - if (type!=null) { - String erasure = type.getErasure(); - try { - Class erasureClass = Class.forName(erasure); - return klass.isAssignableFrom(erasureClass); - } catch (Exception e) { - //type not resolveable assume its not 'array like' - } - } - return false; + return isArray(type) || isCollection(LIST_TYPE_NAME, type) || isCollection(SET_TYPE_NAME, type); } /** * Check if type can be treated / represented as a sequence node in .yml file */ - public static boolean isSequencable(Type type) { + public boolean isSequencable(Type type) { return isBracketable(type); } @@ -486,21 +470,15 @@ public class TypeUtil { } 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 - //are part of JRE standard libraries. Using eclipse 'type hirearchy' would - //also potentialy be very slow. if (type!=null) { String erasure = type.getErasure(); - if ("java.util.Map".equals(erasure)) { + if (MAP_TYPE_NAME.equals(erasure)) { //quick / easy case. No looking for types and hierarchies required. return true; } try { - IType mapType = findType("java.util.Map"); IType erasureType = findType(erasure); - return isAssignableFrom(mapType, erasureType); + return isAssignableFrom(MAP_TYPE_NAME, erasureType); } catch (Exception e) { //type not resolveable } @@ -508,9 +486,27 @@ public class TypeUtil { return false; } - private boolean isAssignableFrom(IType mapType, IType erasureType) { + private boolean isCollection(String collectionTypeName, Type type) { + if (type!=null) { + String erasure = type.getErasure(); + if (collectionTypeName.equals(erasure)) { + //quick / easy case. No looking for types and hierarchies required. + return true; + } + try { + IType erasureType = findType(erasure); + return isAssignableFrom(collectionTypeName, erasureType); + } catch (Exception e) { + //type not resolveable + } + } + return false; + } + + + private boolean isAssignableFrom(String superTypeName, IType erasureType) { Set seen = new HashSet<>(); - return searchSuperTypes(seen, erasureType, mapType.getFullyQualifiedName()); + return searchSuperTypes(seen, erasureType, superTypeName); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/completions/PropertiesCompletionProposalsCalculator.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/completions/PropertiesCompletionProposalsCalculator.java index 45ce88d9f..aa79e4ea6 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/completions/PropertiesCompletionProposalsCalculator.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/completions/PropertiesCompletionProposalsCalculator.java @@ -264,7 +264,7 @@ public class PropertiesCompletionProposalsCalculator { if (type!=null) { if (typeUtil.isAssignableType(type)) { postfix = "="; - } else if (TypeUtil.isBracketable(type)) { + } else if (typeUtil.isBracketable(type)) { postfix = "["; } else if (typeUtil.isDotable(type)) { postfix = "."; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/hover/PropertiesHoverCalculator.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/hover/PropertiesHoverCalculator.java index 76f330b39..004219db0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/hover/PropertiesHoverCalculator.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/hover/PropertiesHoverCalculator.java @@ -77,22 +77,23 @@ class PropertiesHoverCalculator { if (valueRegion.getStart() <= propertyFinder.offset && propertyFinder.offset < valueRegion.getEnd()) { String valueString = valueRegion.toString(); String propertyName = value.getParent().getKey().decode(); - Type type = getValueType(propertyFinder.index, propertyFinder.typeUtil, propertyName); - if (TypeUtil.isSequencable(type)) { + TypeUtil typeUtil = propertyFinder.typeUtil; + Type type = getValueType(propertyFinder.index, typeUtil, propertyName); + if (typeUtil.isSequencable(type)) { //It is useful to provide content assist for the values in the list when entering a list type = TypeUtil.getDomainType(type); } if (TypeUtil.isClass(type)) { //Special case. We want to provide hoverinfos more liberally than what's suggested for completions (i.e. even class names //that are not suggested by the hints because they do not meet subtyping constraints should be hoverable and linkable! - StsValueHint hint = StsValueHint.className(valueString, propertyFinder.typeUtil); + StsValueHint hint = StsValueHint.className(valueString, typeUtil); if (hint!=null) { return Tuples.of(createRenderable(hint), valueRegion.asRegion()); } } //Hack: pretend to invoke content-assist at the end of the value text. This should provide hints applicable to that value // then show hoverinfo based on that. That way we can avoid duplication a lot of similar logic to compute hoverinfos and hyperlinks. - Collection hints = getValueHints(propertyFinder.index, propertyFinder.typeUtil, valueString, propertyName, EnumCaseMode.ALIASED); + Collection hints = getValueHints(propertyFinder.index, typeUtil, valueString, propertyName, EnumCaseMode.ALIASED); if (hints!=null) { Optional hint = hints.stream().filter(h -> valueString.equals(h.getValue())).findFirst(); if (hint.isPresent()) { 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 2fc448410..fb1c159c0 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 @@ -11,16 +11,16 @@ package org.springframework.ide.vscode.boot.properties.reconcile; -import static org.springframework.ide.vscode.boot.metadata.types.TypeUtil.isBracketable; import static org.springframework.ide.vscode.boot.properties.reconcile.SpringPropertyProblem.problem; import java.util.List; import org.springframework.ide.vscode.boot.metadata.types.Type; import org.springframework.ide.vscode.boot.metadata.types.TypeUtil; -import org.springframework.ide.vscode.boot.metadata.types.TypedProperty; import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.BeanPropertyNameMode; import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode; +import org.springframework.ide.vscode.boot.metadata.types.TypedProperty; +import org.springframework.ide.vscode.boot.properties.completions.SpringPropertiesCompletionEngine; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; import org.springframework.ide.vscode.commons.util.BadLocationException; @@ -87,7 +87,7 @@ public class PropertyNavigator { offset, region.getEnd()-offset)); } } else if (navOp=='[') { - if (isBracketable(type)) { + if (typeUtil.isBracketable(type)) { return bracketNavigate(offset, type); } else { problemCollector.accept(problem(ApplicationPropertiesProblemType.PROP_INVALID_INDEXED_NAVIGATION, 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 a1c8c953c..08530fbcb 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 @@ -119,7 +119,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon if (typeUtil.isMap(type)) { //ready to enter nested map key on next line return "\n"+YamlIndentUtil.INDENT_STR; - } if (TypeUtil.isSequencable(type)) { + } if (typeUtil.isSequencable(type)) { //ready to enter sequence element on next line return "\n- "; } else if (typeUtil.isAtomic(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(); @@ -337,7 +337,7 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon return contextWith(s, TypedProperty.typeOf(subproperties.get(key))); } } else if (s.getType()==YamlPathSegmentType.VAL_AT_INDEX) { - if (TypeUtil.isSequencable(type)) { + if (typeUtil.isSequencable(type)) { return contextWith(s, TypeUtil.getDomainType(type)); } } 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 fc1467aa9..d25f1c6dc 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) { @@ -280,7 +280,7 @@ public class ApplicationYamlASTReconciler implements YamlASTReconciler { private void reconcile(YamlFileAST root, SequenceNode seq, Type type) { if (typeUtil.isAtomic(type)) { expectTypeFoundSequence(type, seq); - } else if (TypeUtil.isSequencable(type)) { + } else if (typeUtil.isSequencable(type)) { Type domainType = TypeUtil.getDomainType(type); if (domainType!=null) { for (Node element : seq.getValue()) {