From ad7203fc7c1ba7677a03dcff3c61d43299ac061d Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Fri, 10 Feb 2017 11:00:12 +0100 Subject: [PATCH] basic property completion support for value annotations added --- vscode-extensions/vscode-boot-java/pom.xml | 9 + .../ConfigurationMetadataGroup.java | 76 +++ .../ConfigurationMetadataHint.java | 74 +++ .../ConfigurationMetadataItem.java | 60 ++ .../ConfigurationMetadataProperty.java | 190 ++++++ .../ConfigurationMetadataRepository.java | 47 ++ ...gurationMetadataRepositoryJsonBuilder.java | 231 +++++++ .../ConfigurationMetadataSource.java | 131 ++++ .../configurationmetadata/Deprecation.java | 66 ++ .../DescriptionExtractor.java | 58 ++ .../boot/configurationmetadata/Hints.java | 82 +++ .../configurationmetadata/JsonReader.java | 195 ++++++ .../boot/configurationmetadata/README.txt | 10 + .../RawConfigurationMetadata.java | 106 ++++ ...SimpleConfigurationMetadataRepository.java | 130 ++++ .../boot/configurationmetadata/ValueHint.java | 97 +++ .../configurationmetadata/ValueProvider.java | 66 ++ .../configurationmetadata/package-info.java | 20 + .../boot/java/BootJavaLanguageServer.java | 5 +- .../ide/vscode/boot/java/Main.java | 4 +- .../completions/BootJavaCompletionEngine.java | 9 +- .../completions/ValueCompletionProcessor.java | 158 +++++ .../completions/ValuePropertyKeyProposal.java | 66 ++ .../boot/metadata/CachingValueProvider.java | 148 +++++ .../boot/metadata/ClassReferenceProvider.java | 154 +++++ .../DefaultSpringPropertyIndexProvider.java | 46 ++ .../vscode/boot/metadata/IndexNavigator.java | 133 +++++ .../boot/metadata/LoggerNameProvider.java | 52 ++ .../boot/metadata/MetadataManipulator.java | 230 +++++++ .../boot/metadata/PropertiesLoader.java | 147 +++++ .../vscode/boot/metadata/PropertyInfo.java | 190 ++++++ .../boot/metadata/ResourceHintProvider.java | 71 +++ .../SpringPropertiesIndexManager.java | 74 +++ .../boot/metadata/SpringPropertyIndex.java | 156 +++++ .../metadata/SpringPropertyIndexProvider.java | 20 + .../boot/metadata/ValueProviderRegistry.java | 98 +++ .../boot/metadata/hints/StsValueHint.java | 137 +++++ .../metadata/hints/ValueHintHoverInfo.java | 33 + .../boot/metadata/util/DeprecationUtil.java | 59 ++ .../vscode/boot/metadata/util/FuzzyMap.java | 170 ++++++ .../vscode/boot/metadata/util/Listener.java | 20 + .../boot/metadata/util/ListenerManager.java | 36 ++ .../completions/test/ScopeCompletionTest.java | 10 +- .../completions/test/ValueCompletionTest.java | 248 ++++++++ .../project/harness/ProjectsHarness.java | 90 +-- .../project/harness/PropertyIndexHarness.java | 565 ++++++++++++++++++ .../.gitignore | 0 .../.mvn/wrapper/maven-wrapper.jar | Bin .../.mvn/wrapper/maven-wrapper.properties | 0 .../mvnw | 0 .../mvnw.cmd | 0 .../pom.xml | 0 .../org/test/TestAnnotationsApplication.java} | 4 +- .../java/org/test/TestScopeCompletion.java | 0 .../java/org/test/TestValueCompletion.java | 17 + 55 files changed, 4703 insertions(+), 95 deletions(-) create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataGroup.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataHint.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataItem.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataProperty.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepository.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilder.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataSource.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/Deprecation.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/DescriptionExtractor.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/Hints.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/JsonReader.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/README.txt create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/RawConfigurationMetadata.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/SimpleConfigurationMetadataRepository.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ValueHint.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ValueProvider.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/package-info.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ValueCompletionProcessor.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ValuePropertyKeyProposal.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/CachingValueProvider.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ClassReferenceProvider.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/DefaultSpringPropertyIndexProvider.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/IndexNavigator.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/LoggerNameProvider.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/MetadataManipulator.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertiesLoader.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertyInfo.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ResourceHintProvider.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertiesIndexManager.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertyIndex.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertyIndexProvider.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ValueProviderRegistry.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/StsValueHint.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/ValueHintHoverInfo.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/DeprecationUtil.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/FuzzyMap.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/Listener.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/ListenerManager.java create mode 100644 vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/boot/java/completions/test/ValueCompletionTest.java create mode 100644 vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/project/harness/PropertyIndexHarness.java rename vscode-extensions/vscode-boot-java/src/test/resources/test-projects/{test-scope-annotation => test-annotations}/.gitignore (100%) rename vscode-extensions/vscode-boot-java/src/test/resources/test-projects/{test-scope-annotation => test-annotations}/.mvn/wrapper/maven-wrapper.jar (100%) rename vscode-extensions/vscode-boot-java/src/test/resources/test-projects/{test-scope-annotation => test-annotations}/.mvn/wrapper/maven-wrapper.properties (100%) rename vscode-extensions/vscode-boot-java/src/test/resources/test-projects/{test-scope-annotation => test-annotations}/mvnw (100%) rename vscode-extensions/vscode-boot-java/src/test/resources/test-projects/{test-scope-annotation => test-annotations}/mvnw.cmd (100%) rename vscode-extensions/vscode-boot-java/src/test/resources/test-projects/{test-scope-annotation => test-annotations}/pom.xml (100%) rename vscode-extensions/vscode-boot-java/src/test/resources/test-projects/{test-scope-annotation/src/main/java/org/test/TestScopeAnnotationApplication.java => test-annotations/src/main/java/org/test/TestAnnotationsApplication.java} (64%) rename vscode-extensions/vscode-boot-java/src/test/resources/test-projects/{test-scope-annotation => test-annotations}/src/main/java/org/test/TestScopeCompletion.java (100%) create mode 100644 vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/src/main/java/org/test/TestValueCompletion.java diff --git a/vscode-extensions/vscode-boot-java/pom.xml b/vscode-extensions/vscode-boot-java/pom.xml index bf2fdca88..7214d4cb3 100644 --- a/vscode-extensions/vscode-boot-java/pom.xml +++ b/vscode-extensions/vscode-boot-java/pom.xml @@ -22,6 +22,10 @@ true + + project-repo + file://${project.basedir}/repo + @@ -33,6 +37,11 @@ + + org.springframework.ide.eclipse + org.json + 1.0 + org.springframework.ide.vscode commons-maven diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataGroup.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataGroup.java new file mode 100644 index 000000000..ea6428e85 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataGroup.java @@ -0,0 +1,76 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * Gather a collection of {@link ConfigurationMetadataProperty properties} that are + * sharing a {@link #getId() common prefix}. Provide access to all the + * {@link ConfigurationMetadataSource sources} that have contributed properties to the + * group. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +@SuppressWarnings("serial") +public class ConfigurationMetadataGroup implements Serializable { + + private final String id; + + private final Map sources = new HashMap(); + + private final Map properties = new HashMap(); + + public ConfigurationMetadataGroup(String id) { + this.id = id; + } + + /** + * Return the id of the group, used as a common prefix for all properties associated + * to it. + * @return the id of the group + */ + public String getId() { + return this.id; + } + + /** + * Return the {@link ConfigurationMetadataSource sources} defining the properties of + * this group. + * @return the sources of the group + */ + public Map getSources() { + return this.sources; + } + + /** + * Return the {@link ConfigurationMetadataProperty properties} defined in this group. + *

+ * A property may appear more than once for a given source, potentially with + * conflicting type or documentation. This is a "merged" view of the properties of + * this group. + * @return the properties of the group + * @see ConfigurationMetadataSource#getProperties() + */ + public Map getProperties() { + return this.properties; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataHint.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataHint.java new file mode 100644 index 000000000..9f5e1f204 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataHint.java @@ -0,0 +1,74 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.util.ArrayList; +import java.util.List; + +/** + * A raw view of a hint used for parsing only. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +class ConfigurationMetadataHint { + + private static final String KEY_SUFFIX = ".keys"; + + private static final String VALUE_SUFFIX = ".values"; + + private String id; + + private final List valueHints = new ArrayList(); + + private final List valueProviders = new ArrayList(); + + public boolean isMapKeyHints() { + return (this.id != null && this.id.endsWith(KEY_SUFFIX)); + } + + public boolean isMapValueHints() { + return (this.id != null && this.id.endsWith(VALUE_SUFFIX)); + } + + public String resolveId() { + if (isMapKeyHints()) { + return this.id.substring(0, this.id.length() - KEY_SUFFIX.length()); + } + if (isMapValueHints()) { + return this.id.substring(0, this.id.length() - VALUE_SUFFIX.length()); + } + return this.id; + } + + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + public List getValueHints() { + return this.valueHints; + } + + public List getValueProviders() { + return this.valueProviders; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataItem.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataItem.java new file mode 100644 index 000000000..4001c4da1 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataItem.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +/** + * An extension of {@link ConfigurationMetadataProperty} that provides a reference to its + * source. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +class ConfigurationMetadataItem extends ConfigurationMetadataProperty { + + private String sourceType; + + private String sourceMethod; + + /** + * The class name of the source that contributed this property. For example, if the + * property was from a class annotated with {@code @ConfigurationProperties} this + * attribute would contain the fully qualified name of that class. + * @return the source type + */ + public String getSourceType() { + return this.sourceType; + } + + public void setSourceType(String sourceType) { + this.sourceType = sourceType; + } + + /** + * The full name of the method (including parenthesis and argument types) that + * contributed this property. For example, the name of a getter in a + * {@code @ConfigurationProperties} annotated class. + * @return the source method + */ + public String getSourceMethod() { + return this.sourceMethod; + } + + public void setSourceMethod(String sourceMethod) { + this.sourceMethod = sourceMethod; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataProperty.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataProperty.java new file mode 100644 index 000000000..997746490 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataProperty.java @@ -0,0 +1,190 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.io.Serializable; +import java.util.List; + +/** + * Define a configuration property. Each property is fully identified by its + * {@link #getId() id} which is composed of a namespace prefix (the + * {@link ConfigurationMetadataGroup#getId() group id}), if any and the {@link #getName() + * name} of the property. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +@SuppressWarnings("serial") +public class ConfigurationMetadataProperty implements Serializable { + + private String id; + + private String name; + + private String type; + + private String description; + + private String shortDescription; + + private Object defaultValue; + + private final Hints hints = new Hints(); + + private Deprecation deprecation; + + /** + * The full identifier of the property, in lowercase dashed form (e.g. + * my.group.simple-property) + * @return the property id + */ + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + /** + * The name of the property, in lowercase dashed form (e.g. simple-property). If this + * item does not belong to any group, the id is returned. + * @return the property name + */ + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * The class name of the data type of the property. For example, + * {@code java.lang.String}. + *

+ * For consistency, the type of a primitive is specified using its wrapper + * counterpart, i.e. {@code boolean} becomes {@code java.lang.Boolean}. If the type + * holds generic information, these are provided as well, i.e. a {@code HashMap} of + * String to Integer would be defined as {@code java.util.HashMap + * }. + *

+ * Note that this class may be a complex type that gets converted from a String as + * values are bound. + * @return the property type + */ + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + + /** + * A description of the property, if any. Can be multi-lines. + * @return the property description + * @see #getShortDescription() + */ + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * A single-line, single-sentence description of this property, if any. + * @return the property short description + * @see #getDescription() + */ + public String getShortDescription() { + return this.shortDescription; + } + + public void setShortDescription(String shortDescription) { + this.shortDescription = shortDescription; + } + + /** + * The default value, if any. + * @return the default value + */ + public Object getDefaultValue() { + return this.defaultValue; + } + + public void setDefaultValue(Object defaultValue) { + this.defaultValue = defaultValue; + } + + /** + * Return the hints of this item. + * @return the hints + */ + public Hints getHints() { + return this.hints; + } + + /** + * The list of well-defined values, if any. If no extra {@link ValueProvider provider} + * is specified, these values are to be considered a closed-set of the available + * values for this item. + * @return the value hints + * @see #getHints() + */ + @Deprecated + public List getValueHints() { + return this.hints.getValueHints(); + } + + /** + * The value providers that are applicable to this item. Only one + * {@link ValueProvider} is enabled for an item: the first in the list that is + * supported should be used. + * @return the value providers + * @see #getHints() + */ + @Deprecated + public List getValueProviders() { + return this.hints.getValueProviders(); + } + + /** + * The {@link Deprecation} for this property, if any. + * @return the deprecation + * @see #isDeprecated() + */ + public Deprecation getDeprecation() { + return this.deprecation; + } + + public void setDeprecation(Deprecation deprecation) { + this.deprecation = deprecation; + } + + /** + * Specify if the property is deprecated. + * @return if the property is deprecated + * @see #getDeprecation() + */ + public boolean isDeprecated() { + return this.deprecation != null; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepository.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepository.java new file mode 100644 index 000000000..95122ac2a --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepository.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.util.Map; + +/** + * A repository of configuration metadata. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +public interface ConfigurationMetadataRepository { + + /** + * Defines the name of the "root" group, that is the group that gathers all the + * properties that aren't attached to a specific group. + */ + String ROOT_GROUP = "_ROOT_GROUP_"; + + /** + * Return the groups, indexed by id. + * @return all configuration meta-data groups + */ + Map getAllGroups(); + + /** + * Return the properties, indexed by id. + * @return all configuration meta-data properties + */ + Map getAllProperties(); + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilder.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilder.java new file mode 100644 index 000000000..b7d096fd8 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataRepositoryJsonBuilder.java @@ -0,0 +1,231 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.ide.eclipse.org.json.JSONException; + +/** + * Load a {@link ConfigurationMetadataRepository} from the content of arbitrary + * resource(s). + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +public final class ConfigurationMetadataRepositoryJsonBuilder { + + /** + * UTF-8 Charset. + */ + public static final Charset UTF_8 = Charset.forName("UTF-8"); + + private Charset defaultCharset = UTF_8; + + private final JsonReader reader = new JsonReader(); + + private final List rawDatas = new ArrayList<>(); + + private ConfigurationMetadataRepositoryJsonBuilder(Charset defaultCharset) { + this.defaultCharset = defaultCharset; + } + + /** + * Add the content of a {@link ConfigurationMetadataRepository} defined by the + * specified {@link InputStream} json document using the default charset. If this + * metadata repository holds items that were loaded previously, these are ignored. + *

+ * Leaves the stream open when done. + * @param origin optional information object to help identify where the inputstream came from + * @param inputStream the source input stream + * @return this builder + * @throws IOException in case of I/O errors + */ + public ConfigurationMetadataRepositoryJsonBuilder withJsonResource( + Object origin, InputStream inputStream) throws IOException { + return withJsonResource(origin, inputStream, this.defaultCharset); + } + + /** + * Add the content of a {@link ConfigurationMetadataRepository} defined by the + * specified {@link InputStream} json document using the specified {@link Charset}. If + * this metadata repository holds items that were loaded previously, these are + * ignored. + *

+ * Leaves the stream open when done. + * @param origin optional information object to help identify where the inputstream came from + * @param inputStream the source input stream + * @param charset the charset of the input + * @return this builder + * @throws IOException in case of I/O errors + */ + public ConfigurationMetadataRepositoryJsonBuilder withJsonResource( + Object origin, InputStream inputStream, Charset charset) throws IOException { + if (inputStream == null) { + throw new IllegalArgumentException("InputStream must not be null."); + } + this.rawDatas.add(parseRaw(origin, inputStream, charset)); + return this; + } + + /** + * Build a {@link ConfigurationMetadataRepository} with the current state of this + * builder. + * @return this builder + */ + public ConfigurationMetadataRepository build() { + SimpleConfigurationMetadataRepository result = new SimpleConfigurationMetadataRepository(); + result.include(create(rawDatas)); + return result; + } + + private RawConfigurationMetadata parseRaw(Object origin, InputStream in, Charset charset) + throws IOException { + try { + return this.reader.read(origin, in, charset); + } + catch (IOException ex) { + throw new IllegalArgumentException( + "Failed to read configuration " + "metadata", ex); + } + catch (JSONException ex) { + throw new IllegalArgumentException( + "Invalid configuration " + "metadata document", ex); + } + } + + private SimpleConfigurationMetadataRepository create( + Iterable metadatas) { + SimpleConfigurationMetadataRepository repository = new SimpleConfigurationMetadataRepository(); + + for (RawConfigurationMetadata metadata : metadatas) { + repository.add(metadata.getSources()); + } + for (RawConfigurationMetadata metadata : metadatas) { + for (ConfigurationMetadataItem item : metadata.getItems()) { + ConfigurationMetadataSource source = getSource(metadata, item); + repository.add(item, source); + } + } + for (RawConfigurationMetadata metadata : metadatas) { + Map allProperties = repository + .getAllProperties(); + for (ConfigurationMetadataHint hint : metadata.getHints()) { + ConfigurationMetadataProperty property = allProperties.get(hint.getId()); + if (property != null) { + addValueHints(property, hint); + } + else { + String id = hint.resolveId(); + property = allProperties.get(id); + if (property != null) { + if (hint.isMapKeyHints()) { + addMapHints(property, hint); + } + else { + addValueHints(property, hint); + } + } + } + } + } + return repository; + } + + private void addValueHints(ConfigurationMetadataProperty property, + ConfigurationMetadataHint hint) { + addAll(property.getHints().getValueHints(), hint.getValueHints()); + property.getHints().getValueProviders().addAll(hint.getValueProviders()); + } + + private void addMapHints(ConfigurationMetadataProperty property, + ConfigurationMetadataHint hint) { + addAll(property.getHints().getKeyHints(), hint.getValueHints()); + property.getHints().getKeyProviders().addAll(hint.getValueProviders()); + } + + /** + * Add a bunch of hints to a list, but guard against duplicates. + */ + private void addAll(List existing, List toAdd) { + if (existing.isEmpty()) { + existing.addAll(toAdd); + } else if (toAdd.isEmpty()) { + //nothing to add + } else { + Set existingValues = existing + .stream() + .map((hint) -> ""+hint.getValue()) + .collect(Collectors.toSet()); + for (ValueHint hint : toAdd) { + if (!existingValues.contains(""+hint.getValue())) { + existing.add(hint); + } + } + } + } + + private ConfigurationMetadataSource getSource(RawConfigurationMetadata metadata, + ConfigurationMetadataItem item) { + if (item.getSourceType() != null) { + return metadata.getSource(item.getSourceType()); + } + return null; + } + + /** + * Create a new builder instance using {@link #UTF_8} as the default charset and the + * specified json resource. + * @param inputStreams the source input streams + * @return a new {@link ConfigurationMetadataRepositoryJsonBuilder} instance. + * @throws IOException on error + */ + public static ConfigurationMetadataRepositoryJsonBuilder create( + InputStream... inputStreams) throws IOException { + ConfigurationMetadataRepositoryJsonBuilder builder = create(); + for (InputStream inputStream : inputStreams) { + builder = builder.withJsonResource(null, inputStream); + } + return builder; + } + + /** + * Create a new builder instance using {@link #UTF_8} as the default charset. + * @return a new {@link ConfigurationMetadataRepositoryJsonBuilder} instance. + */ + public static ConfigurationMetadataRepositoryJsonBuilder create() { + return create(UTF_8); + } + + /** + * Create a new builder instance using the specified default {@link Charset}. + * @param defaultCharset the default charset to use + * @return a new {@link ConfigurationMetadataRepositoryJsonBuilder} instance. + */ + public static ConfigurationMetadataRepositoryJsonBuilder create( + Charset defaultCharset) { + return new ConfigurationMetadataRepositoryJsonBuilder(defaultCharset); + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataSource.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataSource.java new file mode 100644 index 000000000..9c1dad953 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ConfigurationMetadataSource.java @@ -0,0 +1,131 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +/** + * A source of configuration metadata. Also defines where the source is declared, for + * instance if it is defined as a {@code @Bean}. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +@SuppressWarnings("serial") +public class ConfigurationMetadataSource implements Serializable { + + private String groupId; + + private String type; + + private String description; + + private String shortDescription; + + private String sourceType; + + private String sourceMethod; + + private final Map properties = new HashMap(); + + /** + * The identifier of the group to which this source is associated. + * @return the group id + */ + public String getGroupId() { + return this.groupId; + } + + void setGroupId(String groupId) { + this.groupId = groupId; + } + + /** + * The type of the source. Usually this is the fully qualified name of a class that + * defines configuration items. This class may or may not be available at runtime. + * @return the type + */ + public String getType() { + return this.type; + } + + void setType(String type) { + this.type = type; + } + + /** + * A description of this source, if any. Can be multi-lines. + * @return the description + * @see #getShortDescription() + */ + public String getDescription() { + return this.description; + } + + void setDescription(String description) { + this.description = description; + } + + /** + * A single-line, single-sentence description of this source, if any. + * @return the short description + * @see #getDescription() + */ + public String getShortDescription() { + return this.shortDescription; + } + + public void setShortDescription(String shortDescription) { + this.shortDescription = shortDescription; + } + + /** + * The type where this source is defined. This can be identical to the + * {@link #getType() type} if the source is self-defined. + * @return the source type + */ + public String getSourceType() { + return this.sourceType; + } + + void setSourceType(String sourceType) { + this.sourceType = sourceType; + } + + /** + * The method name that defines this source, if any. + * @return the source method + */ + public String getSourceMethod() { + return this.sourceMethod; + } + + void setSourceMethod(String sourceMethod) { + this.sourceMethod = sourceMethod; + } + + /** + * Return the properties defined by this source. + * @return the properties + */ + public Map getProperties() { + return this.properties; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/Deprecation.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/Deprecation.java new file mode 100644 index 000000000..8261a2a85 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/Deprecation.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.io.Serializable; + +/** + * Indicate that a property is deprecated. Provide additional information about the + * deprecation. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +@SuppressWarnings("serial") +public class Deprecation implements Serializable { + + private String reason; + + private String replacement; + + /** + * A reason why the related property is deprecated, if any. Can be multi-lines. + * @return the deprecation reason + */ + public String getReason() { + return this.reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + /** + * The full name of the property that replaces the related deprecated property, if + * any. + * @return the replacement property name + */ + public String getReplacement() { + return this.replacement; + } + + public void setReplacement(String replacement) { + this.replacement = replacement; + } + + @Override + public String toString() { + return "Deprecation{" + "reason='" + this.reason + '\'' + ", replacement='" + + this.replacement + '\'' + '}'; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/DescriptionExtractor.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/DescriptionExtractor.java new file mode 100644 index 000000000..81a9ff9b4 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/DescriptionExtractor.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.text.BreakIterator; +import java.util.Locale; + +/** + * Utility to extract a description. + * + * @author Stephane Nicoll + */ +class DescriptionExtractor { + + private static final String NEW_LINE = System.getProperty("line.separator"); + + public String getShortDescription(String description) { + if (description == null) { + return null; + } + int dot = description.indexOf("."); + if (dot != -1) { + BreakIterator breakIterator = BreakIterator.getSentenceInstance(Locale.US); + breakIterator.setText(description); + String text = description + .substring(breakIterator.first(), breakIterator.next()).trim(); + return removeSpaceBetweenLine(text); + } + else { + String[] lines = description.split(NEW_LINE); + return lines[0].trim(); + } + } + + private String removeSpaceBetweenLine(String text) { + String[] lines = text.split(NEW_LINE); + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + sb.append(line.trim()).append(" "); + } + return sb.toString().trim(); + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/Hints.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/Hints.java new file mode 100644 index 000000000..26bdcb69d --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/Hints.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.util.ArrayList; +import java.util.List; + +/** + * Hints of an item to provide the list of values and/or the name of the provider + * responsible to identify suitable values. If the type of the related item is a + * {@link java.util.Map} it can have both key and value hints. + * + * @author Stephane Nicoll + * @since 1.4.0 + */ +public class Hints { + + private final List keyHints = new ArrayList(); + + private final List keyProviders = new ArrayList(); + + private final List valueHints = new ArrayList(); + + private final List valueProviders = new ArrayList(); + + /** + * The list of well-defined keys, if any. Only applicable if the type of the related + * item is a {@link java.util.Map}. If no extra {@link ValueProvider provider} is + * specified, these values are to be considered a closed-set of the available keys for + * the map. + * @return the key hints + */ + public List getKeyHints() { + return this.keyHints; + } + + /** + * The value providers that are applicable to the keys of this item. Only applicable + * if the type of the related item is a {@link java.util.Map}. Only one + * {@link ValueProvider} is enabled for a key: the first in the list that is supported + * should be used. + * @return the key providers + */ + public List getKeyProviders() { + return this.keyProviders; + } + + /** + * The list of well-defined values, if any. If no extra {@link ValueProvider provider} + * is specified, these values are to be considered a closed-set of the available + * values for this item. + * @return the value hints + */ + public List getValueHints() { + return this.valueHints; + } + + /** + * The value providers that are applicable to this item. Only one + * {@link ValueProvider} is enabled for an item: the first in the list that is + * supported should be used. + * @return the value providers + */ + public List getValueProviders() { + return this.valueProviders; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/JsonReader.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/JsonReader.java new file mode 100644 index 000000000..4d73902c9 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/JsonReader.java @@ -0,0 +1,195 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.springframework.ide.eclipse.org.json.JSONArray; +import org.springframework.ide.eclipse.org.json.JSONObject; + +/** + * Read standard json metadata format as {@link ConfigurationMetadataRepository}. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +class JsonReader { + + private static final int BUFFER_SIZE = 4096; + + private final DescriptionExtractor descriptionExtractor = new DescriptionExtractor(); + + public RawConfigurationMetadata read(Object origin, InputStream in, Charset charset) + throws IOException { + JSONObject json = readJson(in, charset); + List groups = parseAllSources(json); + List items = parseAllItems(json); + List hints = parseAllHints(json); + return new RawConfigurationMetadata(origin, groups, items, hints); + } + + private List parseAllSources(JSONObject root) { + List result = new ArrayList(); + if (!root.has("groups")) { + return result; + } + JSONArray sources = root.getJSONArray("groups"); + for (int i = 0; i < sources.length(); i++) { + JSONObject source = sources.getJSONObject(i); + result.add(parseSource(source)); + } + return result; + } + + private List parseAllItems(JSONObject root) { + List result = new ArrayList(); + if (!root.has("properties")) { + return result; + } + JSONArray items = root.getJSONArray("properties"); + for (int i = 0; i < items.length(); i++) { + JSONObject item = items.getJSONObject(i); + result.add(parseItem(item)); + } + return result; + } + + private List parseAllHints(JSONObject root) { + List result = new ArrayList(); + if (!root.has("hints")) { + return result; + } + JSONArray items = root.getJSONArray("hints"); + for (int i = 0; i < items.length(); i++) { + JSONObject item = items.getJSONObject(i); + result.add(parseHint(item)); + } + return result; + } + + private ConfigurationMetadataSource parseSource(JSONObject json) { + ConfigurationMetadataSource source = new ConfigurationMetadataSource(); + source.setGroupId(json.getString("name")); + source.setType(json.optString("type", null)); + String description = json.optString("description", null); + source.setDescription(description); + source.setShortDescription( + this.descriptionExtractor.getShortDescription(description)); + source.setSourceType(json.optString("sourceType", null)); + source.setSourceMethod(json.optString("sourceMethod", null)); + return source; + } + + private ConfigurationMetadataItem parseItem(JSONObject json) { + ConfigurationMetadataItem item = new ConfigurationMetadataItem(); + item.setId(json.getString("name")); + item.setType(json.optString("type", null)); + String description = json.optString("description", null); + item.setDescription(description); + item.setShortDescription( + this.descriptionExtractor.getShortDescription(description)); + item.setDefaultValue(readItemValue(json.opt("defaultValue"))); + item.setDeprecation(parseDeprecation(json)); + item.setSourceType(json.optString("sourceType", null)); + item.setSourceMethod(json.optString("sourceMethod", null)); + return item; + } + + private ConfigurationMetadataHint parseHint(JSONObject json) { + ConfigurationMetadataHint hint = new ConfigurationMetadataHint(); + hint.setId(json.getString("name")); + if (json.has("values")) { + JSONArray values = json.getJSONArray("values"); + for (int i = 0; i < values.length(); i++) { + JSONObject value = values.getJSONObject(i); + ValueHint valueHint = new ValueHint(); + valueHint.setValue(readItemValue(value.get("value"))); + String description = value.optString("description", null); + valueHint.setDescription(description); + valueHint.setShortDescription( + this.descriptionExtractor.getShortDescription(description)); + hint.getValueHints().add(valueHint); + } + } + if (json.has("providers")) { + JSONArray providers = json.getJSONArray("providers"); + for (int i = 0; i < providers.length(); i++) { + JSONObject provider = providers.getJSONObject(i); + ValueProvider valueProvider = new ValueProvider(); + valueProvider.setName(provider.getString("name")); + if (provider.has("parameters")) { + JSONObject parameters = provider.getJSONObject("parameters"); + Iterator keys = parameters.keys(); + while (keys.hasNext()) { + String key = (String) keys.next(); + valueProvider.getParameters().put(key, + readItemValue(parameters.get(key))); + } + } + hint.getValueProviders().add(valueProvider); + } + } + return hint; + } + + private Deprecation parseDeprecation(JSONObject object) { + if (object.has("deprecation")) { + JSONObject deprecationJsonObject = object.getJSONObject("deprecation"); + Deprecation deprecation = new Deprecation(); + deprecation.setReason(deprecationJsonObject.optString("reason", null)); + deprecation + .setReplacement(deprecationJsonObject.optString("replacement", null)); + return deprecation; + } + return (object.optBoolean("deprecated") ? new Deprecation() : null); + } + + private Object readItemValue(Object value) { + if (value instanceof JSONArray) { + JSONArray array = (JSONArray) value; + Object[] content = new Object[array.length()]; + for (int i = 0; i < array.length(); i++) { + content[i] = array.get(i); + } + return content; + } + return value; + } + + private JSONObject readJson(InputStream in, Charset charset) throws IOException { + try { + StringBuilder out = new StringBuilder(); + InputStreamReader reader = new InputStreamReader(in, charset); + char[] buffer = new char[BUFFER_SIZE]; + int bytesRead = -1; + while ((bytesRead = reader.read(buffer)) != -1) { + out.append(buffer, 0, bytesRead); + } + return new JSONObject(out.toString()); + } + finally { + in.close(); + } + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/README.txt b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/README.txt new file mode 100644 index 000000000..f3e11fe93 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/README.txt @@ -0,0 +1,10 @@ +The source code in this package is taken from here: + +https://github.com/spring-projects/spring-boot/tree/fca6dbaf09c32202d9d958f815221aad54b9fc7b/spring-boot-tools/spring-boot-configuration-metadata/src/main/java/org/springframework/boot/configurationmetadata + +Notes: + - This commit is from the master branch at a point in time where boot team is working on Boot 1.4.x on that branch. + +There are currently no modifications being made to that code at all to accomodate STS. So it may now be possible to consume it as a proper dependency. +However, keep in mind that we are using a modified copy of 'org.json' to allow controlling key order in json maps. So that probably +complicates things. diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/RawConfigurationMetadata.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/RawConfigurationMetadata.java new file mode 100644 index 000000000..a264b1e9b --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/RawConfigurationMetadata.java @@ -0,0 +1,106 @@ +/* + * Copyright 2012-2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.util.ArrayList; +import java.util.List; + +/** + * A raw metadata structure. Used to initialize a {@link ConfigurationMetadataRepository}. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +class RawConfigurationMetadata { + + private final Object origin; + + private final List sources; + + private final List items; + + private final List hints; + + RawConfigurationMetadata(Object parsedFrom, + List sources, + List items, + List hints) { + this.origin = parsedFrom; + this.sources = new ArrayList(sources); + this.items = new ArrayList(items); + this.hints = new ArrayList(hints); + for (ConfigurationMetadataItem item : this.items) { + resolveName(item); + } + } + + public List getSources() { + return this.sources; + } + + public ConfigurationMetadataSource getSource(String type) { + for (ConfigurationMetadataSource source : this.sources) { + if (type.equals(source.getType())) { + return source; + } + } + return null; + } + + public List getItems() { + return this.items; + } + + public List getHints() { + return this.hints; + } + + /** + * Resolve the name of an item against this instance. + * @param item the item to resolve + * @see ConfigurationMetadataProperty#setName(String) + */ + private void resolveName(ConfigurationMetadataItem item) { + item.setName(item.getId()); // fallback + if (item.getSourceType() == null) { + return; + } + ConfigurationMetadataSource source = getSource(item.getSourceType()); + if (source != null) { + String groupId = source.getGroupId(); + String dottedPrefix = groupId + "."; + String id = item.getId(); + if (hasLength(groupId) && id.startsWith(dottedPrefix)) { + String name = id.substring(dottedPrefix.length(), id.length()); + item.setName(name); + } + } + } + + private static boolean hasLength(String string) { + return (string != null && string.length() > 0); + } + + @Override + public String toString() { + if (origin!=null) { + return "RawConfigurationMetadata("+origin+")"; + } + return super.toString(); + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/SimpleConfigurationMetadataRepository.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/SimpleConfigurationMetadataRepository.java new file mode 100644 index 000000000..e12ec4479 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/SimpleConfigurationMetadataRepository.java @@ -0,0 +1,130 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * The default {@link ConfigurationMetadataRepository} implementation. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +@SuppressWarnings("serial") +public class SimpleConfigurationMetadataRepository + implements ConfigurationMetadataRepository, Serializable { + + private final Map allGroups = new HashMap(); + + @Override + public Map getAllGroups() { + return Collections.unmodifiableMap(this.allGroups); + } + + @Override + public Map getAllProperties() { + Map properties = new HashMap(); + for (ConfigurationMetadataGroup group : this.allGroups.values()) { + properties.putAll(group.getProperties()); + } + return properties; + } + + /** + * Register the specified {@link ConfigurationMetadataSource sources}. + * @param sources the sources to add + */ + public void add(Collection sources) { + for (ConfigurationMetadataSource source : sources) { + String groupId = source.getGroupId(); + ConfigurationMetadataGroup group = this.allGroups.get(groupId); + if (group == null) { + group = new ConfigurationMetadataGroup(groupId); + this.allGroups.put(groupId, group); + } + String sourceType = source.getType(); + if (sourceType != null) { + putIfAbsent(group.getSources(), sourceType, source); + } + } + } + + /** + * Add a {@link ConfigurationMetadataProperty} with the + * {@link ConfigurationMetadataSource source} that defines it, if any. + * @param property the property to add + * @param source the source + */ + public void add(ConfigurationMetadataProperty property, + ConfigurationMetadataSource source) { + if (source != null) { + putIfAbsent(source.getProperties(), property.getId(), property); + } + putIfAbsent(getGroup(source).getProperties(), property.getId(), property); + } + + /** + * Merge the content of the specified repository to this repository. + * @param repository the repository to include + */ + public void include(ConfigurationMetadataRepository repository) { + for (ConfigurationMetadataGroup group : repository.getAllGroups().values()) { + ConfigurationMetadataGroup existingGroup = this.allGroups.get(group.getId()); + if (existingGroup == null) { + this.allGroups.put(group.getId(), group); + } + else { + // Merge properties + for (Map.Entry entry : group + .getProperties().entrySet()) { + putIfAbsent(existingGroup.getProperties(), entry.getKey(), + entry.getValue()); + } + // Merge sources + for (Map.Entry entry : group + .getSources().entrySet()) { + putIfAbsent(existingGroup.getSources(), entry.getKey(), + entry.getValue()); + } + } + } + + } + + private ConfigurationMetadataGroup getGroup(ConfigurationMetadataSource source) { + if (source == null) { + ConfigurationMetadataGroup rootGroup = this.allGroups.get(ROOT_GROUP); + if (rootGroup == null) { + rootGroup = new ConfigurationMetadataGroup(ROOT_GROUP); + this.allGroups.put(ROOT_GROUP, rootGroup); + } + return rootGroup; + } + return this.allGroups.get(source.getGroupId()); + } + + private void putIfAbsent(Map map, String key, V value) { + if (!map.containsKey(key)) { + map.put(key, value); + } + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ValueHint.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ValueHint.java new file mode 100644 index 000000000..043fc10dc --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ValueHint.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.io.Serializable; + +/** + * Hint for a value a given property may have. Provide the value and an optional + * description. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +@SuppressWarnings("serial") +public class ValueHint implements Serializable, Cloneable { + + public static ValueHint withValue(Object value) { + ValueHint hint = new ValueHint(); + hint.setValue(value); + return hint; + } + + public ValueHint prefixWith(String prefix) { + try { + ValueHint clone = (ValueHint) this.clone(); + clone.setValue(prefix+value); + return clone; + } catch (CloneNotSupportedException e) { + //This is supposed to be impossble. + throw new RuntimeException(e); + } + } + + private Object value; + + private String description; + + private String shortDescription; + + /** + * Return the hint value. + * @return the value + */ + public Object getValue() { + return this.value; + } + + public void setValue(Object value) { + this.value = value; + } + + /** + * A description of this value, if any. Can be multi-lines. + * @return the description + * @see #getShortDescription() + */ + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + } + + /** + * A single-line, single-sentence description of this hint, if any. + * @return the short description + * @see #getDescription() + */ + public String getShortDescription() { + return this.shortDescription; + } + + public void setShortDescription(String shortDescription) { + this.shortDescription = shortDescription; + } + + @Override + public String toString() { + return "ValueHint{" + "value=" + this.value + ", description='" + this.description + + '\'' + '}'; + } +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ValueProvider.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ValueProvider.java new file mode 100644 index 000000000..550181ee9 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/ValueProvider.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.configurationmetadata; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Define a component that is able to provide the values of a property. + *

+ * Each provider is defined by a {@code name} and can have an arbitrary number of + * {@code parameters}. The available providers are defined in the Spring Boot + * documentation. + * + * @author Stephane Nicoll + * @since 1.3.0 + */ +@SuppressWarnings("serial") +public class ValueProvider implements Serializable { + + private String name; + + private final Map parameters = new LinkedHashMap(); + + /** + * Return the name of the provider. + * @return the name + */ + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * Return the parameters. + * @return the parameters + */ + public Map getParameters() { + return this.parameters; + } + + @Override + public String toString() { + return "ValueProvider{" + "name='" + this.name + ", parameters=" + this.parameters + + '}'; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/package-info.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/package-info.java new file mode 100644 index 000000000..e25ef89eb --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/boot/configurationmetadata/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Spring Boot configuration meta-data parser. + */ +package org.springframework.boot.configurationmetadata; diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java index 79efacdd4..ba51658a0 100644 --- a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java @@ -15,6 +15,7 @@ import org.eclipse.lsp4j.ServerCapabilities; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.springframework.ide.vscode.boot.java.completions.BootJavaCompletionEngine; import org.springframework.ide.vscode.boot.java.completions.BootJavaReconcileEngine; +import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.commons.gradle.GradleCore; import org.springframework.ide.vscode.commons.gradle.GradleProjectFinderStrategy; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine; @@ -46,7 +47,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { private final JavaProjectFinder javaProjectFinder; private final VscodeCompletionEngineAdapter completionEngine; - public BootJavaLanguageServer(JavaProjectFinder javaProjectFinder) { + public BootJavaLanguageServer(JavaProjectFinder javaProjectFinder, SpringPropertyIndexProvider indexProvider) { this.javaProjectFinder = javaProjectFinder; SimpleTextDocumentService documents = getTextDocumentService(); @@ -56,7 +57,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { validateWith(doc, reconcileEngine); }); - ICompletionEngine bootCompletionEngine = new BootJavaCompletionEngine(javaProjectFinder); + ICompletionEngine bootCompletionEngine = new BootJavaCompletionEngine(javaProjectFinder, indexProvider); completionEngine = new VscodeCompletionEngineAdapter(this, bootCompletionEngine); completionEngine.setMaxCompletionsNumber(100); documents.onCompletion(completionEngine::getCompletions); diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/Main.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/Main.java index d8b545050..a1a5664de 100644 --- a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/Main.java +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/Main.java @@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.java; import java.io.IOException; +import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider; import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; @@ -26,7 +27,8 @@ public class Main { public static void main(String[] args) throws IOException, InterruptedException { LaunguageServerApp.start(() -> { JavaProjectFinder javaProjectFinder = BootJavaLanguageServer.DEFAULT_PROJECT_FINDER; - SimpleLanguageServer server = new BootJavaLanguageServer(javaProjectFinder); + DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(javaProjectFinder); + SimpleLanguageServer server = new BootJavaLanguageServer(javaProjectFinder, indexProvider); return server; }); } diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaCompletionEngine.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaCompletionEngine.java index 8d20b070c..6411c5e39 100644 --- a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaCompletionEngine.java +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaCompletionEngine.java @@ -25,6 +25,7 @@ import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.NodeFinder; +import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine; @@ -38,11 +39,14 @@ import org.springframework.ide.vscode.commons.util.text.IDocument; public class BootJavaCompletionEngine implements ICompletionEngine { private static final String SPRING_SCOPE = "org.springframework.context.annotation.Scope"; + private static final String SPRING_VALUE = "org.springframework.beans.factory.annotation.Value"; private JavaProjectFinder projectFinder; + private SpringPropertyIndexProvider indexProvider; - public BootJavaCompletionEngine(JavaProjectFinder projectFinder) { + public BootJavaCompletionEngine(JavaProjectFinder projectFinder, SpringPropertyIndexProvider indexProvider) { this.projectFinder = projectFinder; + this.indexProvider = indexProvider; } @Override @@ -105,6 +109,9 @@ public class BootJavaCompletionEngine implements ICompletionEngine { if (type.getQualifiedName().equals(SPRING_SCOPE)) { new ScopeCompletionProcessor().collectCompletionsForScopeAnnotation(node, annotation, type, completions, offset, doc); } + else if (type.getQualifiedName().equals(SPRING_VALUE)) { + new ValueCompletionProcessor(indexProvider.getIndex(doc)).collectCompletionsForValueAnnotation(node, annotation, type, completions, offset, doc); + } } private String[] getClasspathEntries(IDocument doc) throws Exception { diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ValueCompletionProcessor.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ValueCompletionProcessor.java new file mode 100644 index 000000000..4c9d018b5 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ValueCompletionProcessor.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * 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.completions; + +import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseToHyphens; + +import java.util.List; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.springframework.ide.vscode.boot.metadata.PropertyInfo; +import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap; +import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap.Match; +import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; +import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; +import org.springframework.ide.vscode.commons.util.text.IDocument; + +/** + * @author Martin Lippert + */ +public class ValueCompletionProcessor { + + private FuzzyMap index; + + public ValueCompletionProcessor(FuzzyMap index) { + this.index = index; + } + + public void collectCompletionsForValueAnnotation(ASTNode node, Annotation annotation, ITypeBinding type, + List completions, int offset, IDocument doc) { + + try { + // case: @Value(<*>) + if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) { + List> matches = findMatches(""); + + for (Match match : matches) { + + DocumentEdits edits = new DocumentEdits(doc); + edits.replace(offset, offset, "\"${" + match.data.getId() + "}\""); + + ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match.data.getId(), match.data.getName(), null); + completions.add(proposal); + } + } + // case: @Value(prefix<*>) + else if (node instanceof SimpleName && node.getParent() instanceof Annotation) { + String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition()); + + int startOffset = node.getStartPosition(); + int endOffset = node.getStartPosition() + node.getLength(); + + String proposalPrefix = "\""; + String proposalPostfix = "\""; + + List> matches = findMatches(prefix); + + for (Match match : matches) { + + DocumentEdits edits = new DocumentEdits(doc); + edits.replace(startOffset, endOffset, proposalPrefix + "${" + match.data.getId() + "}" + proposalPostfix); + + ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match.data.getId(), match.data.getName(), null); + completions.add(proposal); + } + } + // case: @Value("prefix<*>") + else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { + if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { + + String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, offset - (node.getStartPosition() + 1)), offset - (node.getStartPosition() + 1)); + + int startOffset = offset - prefix.length(); + int endOffset = offset; + + + String prePrefix = doc.get(node.getStartPosition() + 1, offset - prefix.length() - node.getStartPosition() - 1); + + String preCompletion; + if (prePrefix.endsWith("${")) { + preCompletion = ""; + } + else if (prePrefix.endsWith("$")) { + preCompletion = "{"; + } + else { + preCompletion = "${"; + } + + String fullNodeContent = doc.get(node.getStartPosition(), node.getLength()); + String postCompletion = isClosingBracketMissing(fullNodeContent + preCompletion) ? "}" : ""; + + List> matches = findMatches(prefix); + + for (Match match : matches) { + + DocumentEdits edits = new DocumentEdits(doc); + edits.replace(startOffset, endOffset, preCompletion + match.data.getId() + postCompletion); + + ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match.data.getId(), match.data.getName(), null); + completions.add(proposal); + } + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + } + + private boolean isClosingBracketMissing(String fullNodeContent) { + int bracketOpens = 0; + + for (int i = 0; i < fullNodeContent.length(); i++) { + if (fullNodeContent.charAt(i) == '{') { + bracketOpens++; + } + else if (fullNodeContent.charAt(i) == '}') { + bracketOpens--; + } + } + + return bracketOpens > 0; + } + + public String identifyPropertyPrefix(String nodeContent, int offset) { + String result = nodeContent.substring(0, offset); + + int i = offset - 1; + while (i >= 0) { + char c = nodeContent.charAt(i); + if (c == '}' || c == '{' || c == '$' || c == '#') { + result = result.substring(i + 1, offset); + break; + } + i--; + } + + return result; + } + + private List> findMatches(String prefix) { + List> matches = index.find(camelCaseToHyphens(prefix)); + return matches; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ValuePropertyKeyProposal.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ValuePropertyKeyProposal.java new file mode 100644 index 000000000..3ecf24c09 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ValuePropertyKeyProposal.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * 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.completions; + +import org.eclipse.lsp4j.CompletionItemKind; +import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; +import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.text.IDocument; + +/** + * @author Martin Lippert + */ +public class ValuePropertyKeyProposal implements ICompletionProposal { + + private DocumentEdits edits; + private String label; + private String detail; + private Renderable documentation; + + public ValuePropertyKeyProposal(DocumentEdits edits, String label, String detail, Renderable documentation) { + this.edits = edits; + this.label = label; + this.detail = detail; + this.documentation = documentation; + } + + @Override + public ICompletionProposal deemphasize() { + return null; + } + + @Override + public String getLabel() { + return this.label; + } + + @Override + public CompletionItemKind getKind() { + return CompletionItemKind.Property; + } + + @Override + public DocumentEdits getTextEdit() { + return this.edits; + } + + @Override + public String getDetail() { + return this.detail; + } + + @Override + public Renderable getDocumentation() { + return this.documentation; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/CachingValueProvider.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/CachingValueProvider.java new file mode 100644 index 000000000..2b009422b --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/CachingValueProvider.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2016 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.metadata; + +import java.time.Duration; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy; +import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.util.FuzzyMatcher; +import org.springframework.ide.vscode.commons.util.Log; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader.InvalidCacheLoadException; + +import reactor.core.publisher.Flux; +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; + +/** + * A abstract {@link ValueProviderStrategy} that is mean to help speedup successive invocations of + * content assist with a similar 'query' string. + *

+ * This implementation is meant to be used for providers that use potentially lenghty/expensive searches + * to determine hints. Since content assist hints are requested by Eclipse CA framework directly on + * the UI thread, they can not simply perform a lengthy search and block UI thread until it finished. + *

+ * This implementation therefore does the following: + *

    + *
  • Limit the duration of time spent on the UI thread. + *
  • Cache results of searches for a limited time. + *
  • Speedup queries for successive queries by using the already cached result of a similar (prefix) query. + *
  • When the time spent on UI thread waiting for a current search exceeds the allowed time limit, + * return immediately with whatever results have been found so far. + *
+ * + * TODO: rather than an abstract class this should really be 'Wrapper' class that delegates to another + * {@link ValueProviderStrategy} and adds a cache in front of it. + * + * @author Kris De Volder + */ +public abstract class CachingValueProvider implements ValueProviderStrategy { + + private static final Duration DEFAULT_TIMEOUT = Duration.ofMillis(1000); + + /** + * Content assist is called inside UI thread and so doing something lenghty things + * like a JavaSearch will block the UI thread completely freezing the UI. So, we + * only return as many results as can be obtained within this hard TIMEOUT limit. + */ + public static Duration TIMEOUT = DEFAULT_TIMEOUT; + + /** + * The maximum number of results returned for a single request. Used to limit the + * values that are cached per entry. + */ + private int MAX_RESULTS = 500; + + private Cache, CacheEntry> cache = createCache(); + + private class CacheEntry { + boolean isComplete = false; + int count = 0; + Flux values; + + public CacheEntry(String query, Flux producer) { + values = producer + .take(MAX_RESULTS) + .cache(MAX_RESULTS); + values.subscribe(); // create infinite demand so that we actually force cache entries to be fetched upto the max. + } + + @Override + public String toString() { + return "CacheEntry [isComplete=" + isComplete + ", count=" + count + "]"; + } + + } + + @Override + public final Flux getValues(IJavaProject javaProject, String query) { + Tuple2 key = key(javaProject, query); + CacheEntry cached = null; + try { + cached = cache.get(key, () -> new CacheEntry(query, getValuesIncremental(javaProject, query))); + } catch (ExecutionException e) { + Log.log(e); + } + return cached.values; + } + + /** + * Tries to use an already cached, complete result for a query that is a prefix of the current query to speed things up. + *

+ * Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache. + */ + private Flux getValuesIncremental(IJavaProject javaProject, String query) { +// debug("trying to solve "+query+" incrementally"); + String subquery = query; + while (subquery.length()>=1) { + subquery = subquery.substring(0, subquery.length()-1); + CacheEntry cached = null; + try { + cached = cache.get(key(javaProject, subquery), () -> null); + } catch (ExecutionException | InvalidCacheLoadException e) { +// Log.log(e); + } + if (cached!=null) { + System.out.println("cached "+subquery+": "+cached); + if (cached.isComplete) { + return cached.values +// .doOnNext((hint) -> debug("filter["+query+"]: "+hint.getValue())) + .filter((hint) -> 0!=FuzzyMatcher.matchScore(query, hint.getValue().toString())); + } else { +// debug("subquery "+subquery+" cached but is incomplete"); + } + } + } +// debug("full search for: "+query); + return getValuesAsync(javaProject, query); + } + + protected abstract Flux getValuesAsync(IJavaProject javaProject, String query); + + private Tuple2 key(IJavaProject javaProject, String query) { + return Tuples.of(javaProject==null?null:javaProject.getElementName(), query); + } + + protected Cache createCache() { + return CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).expireAfterAccess(1, TimeUnit.MINUTES).build(); + } + + public static void restoreDefaults() { + TIMEOUT = DEFAULT_TIMEOUT; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ClassReferenceProvider.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ClassReferenceProvider.java new file mode 100644 index 000000000..2618837bc --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ClassReferenceProvider.java @@ -0,0 +1,154 @@ +/******************************************************************************* + * Copyright (c) 2016-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.metadata; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy; +import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint; +import org.springframework.ide.vscode.commons.java.Flags; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.util.Log; +import org.springframework.ide.vscode.commons.util.StringUtil; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import reactor.core.publisher.Flux; + +/** + * Provides the algorithm for 'class-reference' valueProvider. + *

+ * See: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-docs/src/main/asciidoc/appendix-configuration-metadata.adoc + * + * @author Kris De Volder + * @author Alex Boyko + */ +public class ClassReferenceProvider extends CachingValueProvider { + + /** + * Default value for the 'concrete' parameter. + */ + private static final boolean DEFAULT_CONCRETE = true; + + private static final ClassReferenceProvider UNTARGETTED_INSTANCE = new ClassReferenceProvider(null, DEFAULT_CONCRETE); + + public static final Function, ValueProviderStrategy> FACTORY = applyOn( + 1, TimeUnit.MINUTES, + (params) -> { + String target = getTarget(params); + Boolean concrete = getConcrete(params); + if (target!=null || concrete!=null) { + if (concrete==null) { + concrete = DEFAULT_CONCRETE; + } + return new ClassReferenceProvider(target, concrete); + } + return UNTARGETTED_INSTANCE; + } + ); + + private static Function applyOn(long duration, TimeUnit unit, Function func) { + Cache cache = CacheBuilder.newBuilder().expireAfterAccess(duration, unit).expireAfterWrite(duration, unit).build(); + return (k) -> { + try { + return cache.get(k, () -> func.apply(k)); + } catch (ExecutionException e) { + Log.log(e); + return null; + } + }; + } + + private static String getTarget(Map params) { + if (params!=null) { + Object obj = params.get("target"); + if (obj instanceof String) { + String target = (String) obj; + if (StringUtil.hasText(target)) { + return target; + } + } + } + return null; + } + + private static boolean isAbstract(IType type) { + try { + return type.isInterface() || Flags.isAbstract(type.getFlags()); + } catch (Exception e) { + Log.log(e); + return false; + } + } + + private static Boolean getConcrete(Map params) { + try { + if (params!=null) { + Object obj = params.get("concrete"); + if (obj instanceof String) { + String concrete = (String) obj; + return Boolean.valueOf(concrete); + } else if (obj instanceof Boolean) { + return (Boolean) obj; + } + } + } catch (Exception e) { + Log.log(e); + } + return null; + } + + /** + * Optional, fully qualified name of the 'target' type. Suggested hints should be a subtype of this type. + */ + private String target; + + /** + * Optional parameter, whether only concrete types should be suggested. Default value is true. + */ + private boolean concrete; + + private ClassReferenceProvider(String target, boolean concrete) { + this.target = target; + this.concrete = concrete; + } + + @Override + protected Flux getValuesAsync(IJavaProject javaProject, String query) { + IType targetType = target == null || target.isEmpty() ? javaProject.getClasspath().findType("java.lang.Object") : javaProject.getClasspath().findType(target); + if (targetType == null) { + return Flux.empty(); + } + Set allSubclasses = javaProject.getClasspath() + .allSubtypesOf(targetType) + .filter(t -> Flags.isPublic(t.getFlags()) && !concrete || !isAbstract(t)) + .collect(Collectors.toSet()) + .block(); + if (allSubclasses.isEmpty()) { + return Flux.empty(); + } else { + return javaProject.getClasspath() + .fuzzySearchTypes(query, type -> allSubclasses.contains(type)) + .collectSortedList((o1, o2) -> o2.getT2().compareTo(o1.getT2())) + .flatMap(l -> Flux.fromIterable(l)) + .map(t -> StsValueHint.create(t.getT1())); + } + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/DefaultSpringPropertyIndexProvider.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/DefaultSpringPropertyIndexProvider.java new file mode 100644 index 000000000..0e379d645 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/DefaultSpringPropertyIndexProvider.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright (c) 2016, 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.metadata; + +import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.ProgressService; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.util.text.IDocument; + +public class DefaultSpringPropertyIndexProvider implements SpringPropertyIndexProvider { + + private static final FuzzyMap EMPTY_INDEX = new SpringPropertyIndex(null, null); + + private JavaProjectFinder javaProjectFinder; + private SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager(ValueProviderRegistry.getDefault()); + + private ProgressService progressService = (id, msg) -> { /*ignore*/ }; + + public DefaultSpringPropertyIndexProvider(JavaProjectFinder javaProjectFinder) { + this.javaProjectFinder = javaProjectFinder; + } + + @Override + public FuzzyMap getIndex(IDocument doc) { + IJavaProject jp = javaProjectFinder.find(doc); + if (jp!=null) { + return indexManager.get(jp, progressService); + } + return EMPTY_INDEX; + } + + public void setProgressService(ProgressService progressService) { + this.progressService = progressService; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/IndexNavigator.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/IndexNavigator.java new file mode 100644 index 000000000..cd19604c6 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/IndexNavigator.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2015 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.metadata; + +import static org.springframework.ide.vscode.commons.util.StringUtil.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap; +import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap.Match; +import org.springframework.ide.vscode.commons.util.StringUtil; + +/** + * An index navigator allows selecting subset of a property index as if + * navigating the index by selecting on a property + * + * @author Kris De Volder + */ +public class IndexNavigator { + + //Possible opitmization: we could cache prefix match candidate and extended match candidate + // since it is assumed that the index is immutable for the lifetime of + // the index navigator. + + private static final char NAV_CHAR = '.'; + + /** + * Property access in this navigator are interpreted relative + * to this prefix + */ + private String prefix = null; + private FuzzyMap index; + + private IndexNavigator(FuzzyMap index) { + this.index = index; + } + + private IndexNavigator(FuzzyMap index, String prefix) { + this.index = index; + this.prefix = prefix; + } + + public static IndexNavigator with(FuzzyMap index) { + return new IndexNavigator(index); + } + + public IndexNavigator selectSubProperty(String name) { + return new IndexNavigator(index, join(prefix, name)); + } + + protected String join(String prefix, String postfix) { + if (!hasText(prefix)) { + return postfix; + } else { + return prefix + NAV_CHAR + postfix; + } + } + + /** + * @return property info that is an exact match with the current prefix or + * null if there's no exact match + */ + public PropertyInfo getExactMatch() { + if (prefix!=null) { + PropertyInfo candidate = index.findLongestCommonPrefixEntry(prefix); + if (candidate.getId().equals(prefix)) { + return candidate; + } + } + return null; + } + + /** + * Get a property that has the current prefix as a 'true' prefix. A true prefix + * is a String that has the current prefix as a prefix and continues onward with + * a navigation operation. + */ + public PropertyInfo getExtensionCandidate() { + //If current prefix is null then all entries in the index are candidates since + // the index is at the 'root' of the tree and we don't need a '.' to navigate + String extendedPrefix = prefix==null?"":prefix + NAV_CHAR; + PropertyInfo candidate = index.findLongestCommonPrefixEntry(extendedPrefix); + if (candidate.getId().startsWith(extendedPrefix)) { + return candidate; + } + return null; + } + + public String getPrefix() { + return prefix; + } + + public List> findMatching(String query) { + if (!StringUtil.hasText(prefix)) { + return index.find(query); + } else { + String dottedPrefix = prefix +"."; + List> candidates = index.find(dottedPrefix + query); + if (!candidates.isEmpty()) { + //TODO: we can do better than this using treemap to narrow based on + // prefix + List> matches = new ArrayList>(candidates.size()); + for (Match match : candidates) { + if (match.data.getId().startsWith(dottedPrefix)){ + matches.add(match); + } + } + return matches; + } + } + return Collections.emptyList(); + } + + @Override + public String toString() { + return "IndexNavigator("+prefix+")"; + } + + public boolean isEmpty() { + return getExactMatch()==null && getExtensionCandidate()==null; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/LoggerNameProvider.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/LoggerNameProvider.java new file mode 100644 index 000000000..2f9886766 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/LoggerNameProvider.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2016-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.metadata; + +import java.util.Map; +import java.util.function.Function; + +import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy; +import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint; +import org.springframework.ide.vscode.commons.java.IJavaProject; + +import reactor.core.publisher.Flux; +import reactor.util.function.Tuples; + +/** + * Provides the algorithm for 'logger-name' valueProvider. + *

+ * See: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-docs/src/main/asciidoc/appendix-configuration-metadata.adoc + * + * @author Kris De Volder + * @author Alex Boyko + */ +public class LoggerNameProvider extends CachingValueProvider { + + private static final ValueProviderStrategy INSTANCE = new LoggerNameProvider(); + public static final Function, ValueProviderStrategy> FACTORY = (params) -> INSTANCE; + + @Override + protected Flux getValuesAsync(IJavaProject javaProject, String query) { + return Flux.concat( + javaProject.getClasspath() + .fuzzySearchPackages(query) + .map(t -> Tuples.of(StsValueHint.create(t.getT1()), t.getT2())), + javaProject.getClasspath() + .fuzzySearchTypes(query, null) + .map(t -> Tuples.of(StsValueHint.create(t.getT1()), t.getT2())) + ) + .collectSortedList((o1, o2) -> o2.getT2().compareTo(o1.getT2())) + .flatMap(l -> Flux.fromIterable(l)) + .map(t -> t.getT1()); + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/MetadataManipulator.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/MetadataManipulator.java new file mode 100644 index 000000000..328e1f3e1 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/MetadataManipulator.java @@ -0,0 +1,230 @@ +/******************************************************************************* + * Copyright (c) 2015 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.metadata; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.LinkedHashMap; + +import org.springframework.ide.eclipse.org.json.JSONArray; +import org.springframework.ide.eclipse.org.json.JSONObject; + +/** + * Helper class to manipulate data in a file presumed to contain + * spring-boot configuration data. + * + * @author Kris De Volder + * @author Alex Boyko + */ +public class MetadataManipulator { + + private abstract class Content { + public abstract String toString(); + public abstract void addProperty(JSONObject jsonObject) throws Exception; + } + + /** + * Content was parse as JSONObject. + */ + private class ParsedContent extends Content { + + private JSONObject object; + + public ParsedContent(JSONObject o) { + this.object = o; + } + + public String toString() { + return object.toString(indentFactor); + } + + @Override + public void addProperty(JSONObject propertyData) throws Exception { + JSONArray properties = object.getJSONArray("properties"); + properties.put(properties.length(), propertyData); + } + } + + /** + * Content that is 'unparsed' and just a bunch of text. + * Used only as a fallback when data in file can't + * be parsed. + *

+ * This content is manipulated by string manipulation. + * It is less reliable, but can be done even if the + * file data is not parseable. + */ + private class RawContent extends Content { + + private StringBuilder doc; + + public RawContent(String content) { + this.doc = new StringBuilder(content); + } + + @Override + public String toString() { + return doc.toString(); + } + + @Override + public void addProperty(JSONObject propertyData) throws Exception { + int insertAt = findLast(']'); + if (insertAt<0) { + //although we're not looking for much, we didn't find it! + //Funky file contents. Let's just insert something at end of file in a 'best effort' spirit. + insertAt = doc.length(); + } + insert(insertAt, "\n"); + + insert(insertAt, propertyData.toString(indentFactor)); + + int insertComma = findInsertCommaPos(insertAt); + if (insertComma>=0) { + insert(insertComma, ","); + } + } + + /** + * Maybe we need to add a comma in front of the new entry. This + * method finds if/where to stick this comma. + * @throws Exception + */ + private int findInsertCommaPos(int pos) throws Exception { + pos--; + while (pos>=0 && Character.isWhitespace(doc.charAt(pos))) { + pos--; + } + if (pos>=0) { + char c = doc.charAt(pos); + if (c == '}') { + //Add a comma after a '}' + return pos+1; + } + } + return -1; + } + + private int insert(int insertAt, String str) throws Exception { + if (insertAt < doc.length()) { + doc.replace(insertAt, insertAt, str); + } else { + doc.append(str); + } + return insertAt + str.length(); + } + + private int findLast(char toFind) throws Exception { + int pos = doc.length()-1; + while (pos>=0 && doc.charAt(pos)!=toFind) { + pos--; + } + //We got here either because + // - we found char at pos or.. + // - we reached position *before* start of file (i.e. -1) + return pos; + } + + } + + public interface ContentStore { + String getContents() throws Exception; + void setContents(String content) throws Exception; + } + + private static final String INITIAL_CONTENT = + "{\"properties\": [\n" + + "]}"; + + private static final String ENCODING = "UTF8"; + private ContentStore contentStore; + private Content fContent; + private int indentFactor = 2; + + public MetadataManipulator(ContentStore contentStore) { + this.contentStore = contentStore; + } + + public MetadataManipulator(final File file) { + this(new ContentStore() { + + @Override + public String getContents() throws Exception { + return new String(Files.readAllBytes(Paths.get(file.toURI())), ENCODING); + } + + @Override + public void setContents(String content) throws Exception { + Files.write(Paths.get(file.toURI()), content.getBytes(ENCODING)); + } + + }); + } + + private Content getContent() throws Exception { + if (fContent==null) { + fContent = readContent(); + } + return fContent; + } + + private Content readContent() throws Exception { + String content = contentStore.getContents(); + if (content.trim().isEmpty()) { + JSONObject o = initialContent(); + return new ParsedContent(o); + } else { + try { + return new ParsedContent(new JSONObject(content)); + } catch (Exception e) { + //couldn't parse? + return new RawContent(content); + } + } + } + + public void addDefaultInfo(String propertyName) throws Exception { + getContent().addProperty(createDefaultData(propertyName)); + } + + private JSONObject createDefaultData(String propertyName) throws Exception { + JSONObject obj = new JSONObject(new LinkedHashMap()); + obj.put("name", propertyName); + obj.put("type", String.class.getName()); + obj.put("description", "A description for '"+propertyName+"'"); + return obj; + } + + /** + * Generate the initial content (must be generated rather than being a constant to respect newline conventions + * on user's system. + */ + private JSONObject initialContent() throws Exception { + return new JSONObject(INITIAL_CONTENT); + } + + /** + * After manipulating the data, use this to persist changes back to the file. + */ + public void save() throws Exception { + contentStore.setContents(getContent().toString()); + } + + /** + * Determines whether the 'reliable' manipulations can be used (which is the case + * only if the data in the file is valid json). + */ + public boolean isReliable() throws Exception { + return getContent() instanceof ParsedContent; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertiesLoader.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertiesLoader.java new file mode 100644 index 000000000..04a03ee07 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertiesLoader.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2016-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.metadata; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.jar.JarFile; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder; +import org.springframework.ide.vscode.commons.java.IClasspath; + +public class PropertiesLoader { + + private static final String MAIN_SPRING_CONFIGURATION_METADATA_JSON = "META-INF/spring-configuration-metadata.json"; + + public static final String ADDITIONAL_SPRING_CONFIGURATION_METADATA_JSON = "META-INF/additional-spring-configuration-metadata.json"; + + /** + * The default classpath location for config metadata loaded when scanning .jar files on the classpath. + */ + public static final String[] JAR_META_DATA_LOCATIONS = { + MAIN_SPRING_CONFIGURATION_METADATA_JSON + //Not scanning 'additional' metadata because it integrated already in the main data. + }; + + /** + * The default classpath location for config metadata loaded when scanning project output folders. + */ + public static final String[] PROJECT_META_DATA_LOCATIONS = { + MAIN_SPRING_CONFIGURATION_METADATA_JSON, + ADDITIONAL_SPRING_CONFIGURATION_METADATA_JSON + }; + + private static final Logger LOG = Logger.getLogger(PropertiesLoader.class.getName()); + + private ConfigurationMetadataRepositoryJsonBuilder builder = ConfigurationMetadataRepositoryJsonBuilder.create(); + + public ConfigurationMetadataRepository load(IClasspath classPath) { + try { + classPath.getClasspathEntries().forEach(entry -> { + File fileEntry = entry.toFile(); + if (fileEntry.exists()) { + if (fileEntry.isDirectory()) { + loadFromOutputFolder(entry); + } else { + loadFromJar(entry); + } + } + }); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Failed to retrieve classpath", e); + } + ConfigurationMetadataRepository repository = builder.build(); + return repository; + } + + private void loadFromOutputFolder(Path outputFolderPath) { + if (outputFolderPath != null && Files.exists(outputFolderPath)) { + Arrays.stream(PROJECT_META_DATA_LOCATIONS).forEach(mdLoc -> { + loadFromJsonFile(outputFolderPath.resolve(mdLoc)); + }); + } + } + + private void loadFromJsonFile(Path mdf) { + if (Files.exists(mdf)) { + InputStream is = null; + try { + is = Files.newInputStream(mdf); + loadFromInputStream(mdf, is); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Error loading file '" + mdf + "'", e); + } finally { + if (is!=null) { + try { + is.close(); + } catch (IOException e) { + //ignore + } + } + } + } + } + + private void loadFromJar(Path f) { + JarFile jarFile = null; + try { + jarFile = new JarFile(f.toFile()); + //jarDump(jarFile); + for (String loc : JAR_META_DATA_LOCATIONS) { + ZipEntry e = jarFile.getEntry(loc); + if (e!=null) { + loadFrom(jarFile, e); + } + } + } catch (Throwable e) { + LOG.log(Level.SEVERE, "Error loading JAR file", e); + } finally { + if (jarFile!=null) { + try { + jarFile.close(); + } catch (IOException e) { + } + } + } + } + + + private void loadFrom(JarFile jarFile, ZipEntry ze) { + InputStream is = null; + try { + is = jarFile.getInputStream(ze); + loadFromInputStream(jarFile.getName()+"["+ze.getName()+"]", is); + } catch (Throwable e) { + LOG.log(Level.SEVERE, "Error loading JAR file", e); + } finally { + if (is!=null) { + try { + is.close(); + } catch (IOException e) { + } + } + } + } + + private void loadFromInputStream(Object origin, InputStream is) throws IOException { + builder.withJsonResource(origin, is); + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertyInfo.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertyInfo.java new file mode 100644 index 000000000..4b3342f03 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/PropertyInfo.java @@ -0,0 +1,190 @@ +/******************************************************************************* + * Copyright (c) 2014-2016 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.metadata; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataSource; +import org.springframework.boot.configurationmetadata.Deprecation; +import org.springframework.boot.configurationmetadata.ValueHint; +import org.springframework.boot.configurationmetadata.ValueProvider; +import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; + +/** + * Information about a spring property, basically, this is the same as + * + * {@link ConfigurationMetadataProperty} but augmented with information + * about {@link ConfigurationMetadataSource}s that declare the property. + * + * @author Kris De Volder + */ +public class PropertyInfo { + + /** + * Identifies a 'Source'. This is essentially the sames as {@link ConfigurationMetadataSource}. + * We could use {@link ConfigurationMetadataSource} directly, but this only contains + * the info that we actually use so takes less memory. + */ + public static class PropertySource { + private final String sourceType; + private final String sourceMethod; + public PropertySource(ConfigurationMetadataSource source) { + String st = source.getSourceType(); + this.sourceType = st!=null?st:source.getType(); + this.sourceMethod = source.getSourceMethod(); + } + @Override + public String toString() { + return sourceType+"::"+sourceMethod; + } + public String getSourceType() { + return sourceType; + } + public String getSourceMethod() { + return sourceMethod; + } + } + + final private String id; + private String type; + final private String name; + final private Object defaultValue; + final private String description; + private List sources; + private Deprecation deprecation; + private ImmutableList valueHints; + private ImmutableList keyHints; + private ValueProviderStrategy valueProvider; + private ValueProviderStrategy keyProvider; + + public PropertyInfo(String id, String type, String name, + Object defaultValue, String description, + Deprecation deprecation, + List valueHints, + List keyHints, + ValueProviderStrategy valueProvider, + ValueProviderStrategy keyProvider, + List sources) { + super(); + this.id = id; + this.type = type; + this.name = name; + this.defaultValue = defaultValue; + this.description = description; + this.deprecation = deprecation; + this.valueHints = valueHints==null?null:ImmutableList.copyOf(valueHints); + this.keyHints = keyHints==null?null:ImmutableList.copyOf(keyHints); + this.valueProvider = valueProvider; + this.keyProvider = keyProvider; + this.sources = sources; + } + public PropertyInfo(ValueProviderRegistry valueProviders, ConfigurationMetadataProperty prop) { + this( + prop.getId(), + prop.getType(), + prop.getName(), + prop.getDefaultValue(), + prop.getDescription(), + prop.getDeprecation(), + prop.getHints().getValueHints(), + prop.getHints().getKeyHints(), + valueProviders.resolve(prop.getHints().getValueProviders()), + valueProviders.resolve(prop.getHints().getKeyProviders()), + null + ); + for (ValueProvider h : prop.getHints().getValueProviders()) { + if (h.getName().equals("handle-as")) { + handleAs(h.getParameters().get("target")); + } + } + } + private void handleAs(Object targetObject) { +// debug("handle-as "+this.getId()+" -> "+targetObject); + if (targetObject instanceof String) { + this.type = (String)targetObject; + } + } + public String getId() { + return id; + } + public String getType() { + return type; + } + public String getName() { + return name; + } + public Object getDefaultValue() { + return defaultValue; + } + public String getDescription() { + return description; + } + + public List getSources() { + if (sources!=null) { + return sources; + } + return Collections.emptyList(); + } + + @Override + public String toString() { + return "PropertyInfo("+getId()+")"; + } + public void addSource(ConfigurationMetadataSource source) { + if (sources==null) { + sources = new ArrayList(); + } + sources.add(new PropertySource(source)); + } + + public PropertyInfo withId(String alias) { + if (alias.equals(id)) { + return this; + } + return new PropertyInfo(alias, type, name, defaultValue, description, deprecation, valueHints, keyHints, valueProvider, keyProvider, sources); + } + + public void setDeprecation(Deprecation d) { + this.deprecation = d; + } + + public boolean isDeprecated() { + return deprecation!=null; + } + + public String getDeprecationReason() { + return deprecation == null ? null : deprecation.getReason(); + } + + public String getDeprecationReplacement() { + return deprecation == null ? null : deprecation.getReplacement(); + } + + public void addValueHints(List hints) { + Builder builder = ImmutableList.builder(); + builder.addAll(valueHints); + builder.addAll(hints); + valueHints = builder.build(); + } + public void addKeyHints(List hints) { + Builder builder = ImmutableList.builder(); + builder.addAll(keyHints); + builder.addAll(hints); + keyHints = builder.build(); + } +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ResourceHintProvider.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ResourceHintProvider.java new file mode 100644 index 000000000..beb3891ee --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ResourceHintProvider.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2016-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.metadata; + +import java.util.Arrays; +import java.util.stream.Collectors; + +import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy; +import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint; +import org.springframework.ide.vscode.commons.java.IJavaProject; + +import com.google.common.collect.ImmutableList; + +import reactor.core.publisher.Flux; + +/** + * @author Kris De Volder + */ +public class ResourceHintProvider implements ValueProviderStrategy { + + private static String[] CLASSPATH_PREFIXES = { + "classpath:", + "classpath*:" + }; + + private static final String[] URL_PREFIXES = new String[] { + "classpath:", + "classpath*:", + "file:", + "http://", + "https://" + }; + + @Override + public Flux getValues(IJavaProject javaProject, String query) { + for (String prefix : CLASSPATH_PREFIXES) { + if (query.startsWith(prefix)) { + return classpathHints + .getValues(javaProject, query.substring(prefix.length())) + .map((hint) -> hint.prefixWith(prefix)); + } + } + return Flux.fromIterable(urlPrefixHints); + } + + final private ImmutableList urlPrefixHints = ImmutableList.copyOf( + Arrays.stream(URL_PREFIXES) + .map(StsValueHint::create) + .collect(Collectors.toList()) + ); + + private ClasspathHints classpathHints = new ClasspathHints(); + + private static class ClasspathHints extends CachingValueProvider { + @Override + protected Flux getValuesAsync(IJavaProject javaProject, String query) { + return Flux.fromStream(javaProject.getClasspath().getClasspathResources().distinct().map(StsValueHint::create)); + } + } + + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertiesIndexManager.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertiesIndexManager.java new file mode 100644 index 000000000..d88075998 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertiesIndexManager.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2014, 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.metadata; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap; +import org.springframework.ide.vscode.boot.metadata.util.Listener; +import org.springframework.ide.vscode.boot.metadata.util.ListenerManager; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.ProgressService; + +/** + * Support for Reconciling, Content Assist and Hover Text in spring properties + * file all make use of a per-project index of spring properties metadata extracted + * from project's classpath. This Index manager is responsible for keeping at most + * one index per-project and to keep the index up-to-date. + * + * @author Kris De Volder + */ +public class SpringPropertiesIndexManager extends ListenerManager> { + + private Map indexes = null; + private final ValueProviderRegistry valueProviders; + private static int progressIdCt = 0; + + public SpringPropertiesIndexManager(ValueProviderRegistry valueProviders) { + this.valueProviders = valueProviders; + } + + public synchronized FuzzyMap get(IJavaProject project, ProgressService progressService) { + if (indexes==null) { + indexes = new HashMap<>(); + } + SpringPropertyIndex index = indexes.get(project); + if (index==null) { + String progressId = getProgressId(); + if (progressService != null) { + progressService.progressEvent(progressId, "Indexing Spring Boot Properties..."); + } + + index = new SpringPropertyIndex(valueProviders, project.getClasspath()); + indexes.put(project, index); + + if (progressService != null) { + progressService.progressEvent(progressId, null); + } + } + return index; + } + + public synchronized void clear() { + if (indexes!=null) { + indexes.clear(); + for (Listener l : getListeners()) { + l.changed(this); + } + } + } + + private static synchronized String getProgressId() { + return DefaultSpringPropertyIndexProvider.class.getName()+ (progressIdCt++); + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertyIndex.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertyIndex.java new file mode 100644 index 000000000..b21ed7bef --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertyIndex.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2015 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.metadata; + +import java.util.Collection; +import java.util.List; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataGroup; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository; +import org.springframework.boot.configurationmetadata.ConfigurationMetadataSource; +import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap; +import org.springframework.ide.vscode.commons.java.IClasspath; +import org.springframework.ide.vscode.commons.util.StringUtil; + +public class SpringPropertyIndex extends FuzzyMap { + + private ValueProviderRegistry valueProviders; + + public SpringPropertyIndex(ValueProviderRegistry valueProviders, IClasspath projectPath) { + this.valueProviders = valueProviders; + if (projectPath!=null) { +// try { + PropertiesLoader loader = new PropertiesLoader(); + ConfigurationMetadataRepository metadata = loader.load(projectPath); + //^^^ Should be done in bg? It seems fast enough for now. + + Collection allEntries = metadata.getAllProperties().values(); + for (ConfigurationMetadataProperty item : allEntries) { + add(new PropertyInfo(valueProviders, item)); + } + + for (ConfigurationMetadataGroup group : metadata.getAllGroups().values()) { + for (ConfigurationMetadataSource source : group.getSources().values()) { + for (ConfigurationMetadataProperty prop : source.getProperties().values()) { + PropertyInfo info = get(prop.getId()); + info.addSource(source); + } + } + } + + // System.out.println(">>> spring properties metadata loaded "+this.size()+" items==="); + // dumpAsTestData(); + // System.out.println(">>> spring properties metadata loaded "+this.size()+" items==="); +// } catch (Exception e) { +// LOG.log +// } + } + } + + public void add(ConfigurationMetadataProperty propertyInfo) { + add(new PropertyInfo(valueProviders, propertyInfo)); + } + + /** + * Dumps out 'test data' based on the current contents of the index. This is not meant to be + * used in 'production' code. The idea is to call this method during development to dump a + * 'snapshot' of the index onto System.out. The data is printed in a forma so that it can be easily + * pasted/used into JUNit testing code. + */ + public void dumpAsTestData() { + List> allData = this.find(""); + for (Match match : allData) { + PropertyInfo d = match.data; + System.out.println("data(" + +dumpString(d.getId())+", " + +dumpString(d.getType())+", " + +dumpString(d.getDefaultValue())+", " + +dumpString(d.getDescription()) +");" + ); +// for (PropertySource source : d.getSources()) { +// String st = source.getSourceType(); +// String sm = source.getSourceMethod(); +// if (sm!=null) { +// System.out.println(d.getId() +" from: "+st+"::"+sm); +// } +// } + } + } + + private String dumpString(Object v) { + if (v==null) { + return "null"; + } + return dumpString(""+v); + } + + private String dumpString(String s) { + if (s==null) { + return "null"; + } else { + StringBuilder buf = new StringBuilder("\""); + for (char c : s.toCharArray()) { + switch (c) { + case '\r': + buf.append("\\r"); + break; + case '\n': + buf.append("\\n"); + break; + case '\\': + buf.append("\\\\"); + break; + case '\"': + buf.append("\\\""); + break; + default: + buf.append(c); + break; + } + } + buf.append("\""); + return buf.toString(); + } + } + + @Override + protected String getKey(PropertyInfo entry) { + return entry.getId(); + } + + /** + * Find the longest known property that is a prefix of the given name. Here prefix does not mean + * 'string prefix' but a prefix in the sense of treating '.' as a kind of separators. So + * 'prefix' is not allowed to end in the middle of a 'segment'. + */ + public static PropertyInfo findLongestValidProperty(FuzzyMap index, String name) { + int bracketPos = name.indexOf('['); + int endPos = bracketPos>=0?bracketPos:name.length(); + PropertyInfo prop = null; + String prefix = null; + while (endPos>0 && prop==null) { + prefix = name.substring(0, endPos); + String canonicalPrefix = StringUtil.camelCaseToHyphens(prefix); + prop = index.get(canonicalPrefix); + if (prop==null) { + endPos = name.lastIndexOf('.', endPos-1); + } + } + if (prop!=null) { + //We should meet caller's expectation that matched properties returned by this method + // match the names exactly even if we found them using relaxed name matching. + return prop.withId(prefix); + } + return null; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertyIndexProvider.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertyIndexProvider.java new file mode 100644 index 000000000..41f750fb0 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertyIndexProvider.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2015 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.metadata; + +import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap; +import org.springframework.ide.vscode.commons.util.text.IDocument; + + +@FunctionalInterface +public interface SpringPropertyIndexProvider { + FuzzyMap getIndex(IDocument doc); +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ValueProviderRegistry.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ValueProviderRegistry.java new file mode 100644 index 000000000..c7032b3de --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/ValueProviderRegistry.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2016 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.metadata; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.springframework.boot.configurationmetadata.ValueProvider; +import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.util.CollectionUtil; + +import reactor.core.publisher.Flux; + +/** + * An instance of this class serves as a 'registry' that associates known + * {@link ValueProvider} ids to strategy objects used in the computation of completions + * for properties to which the provider is attached. + * + * @author Kris De Volder + */ +public class ValueProviderRegistry { + + private static ValueProviderRegistry DEFAULT; + + /** + * Creates a default {@link ValueProviderRegistry} which is initialized with all the known + * providers. (This is the one production code should use, test code might make use + * something else for mocking purposes). + */ + public synchronized static ValueProviderRegistry getDefault() { + if (DEFAULT==null) { + DEFAULT = new ValueProviderRegistry(); + DEFAULT.initializeDefaults(DEFAULT); + } + return DEFAULT; + } + + protected void initializeDefaults(ValueProviderRegistry r) { + def("logger-name", LoggerNameProvider.FACTORY); + def("class-reference", ClassReferenceProvider.FACTORY); + } + + private Map, ValueProviderStrategy>> registry = new HashMap<>(); + + public interface ValueProviderStrategy { + Flux getValues(IJavaProject javaProject, String query); + + default Collection getValuesNow(IJavaProject javaProject, String query) { + return this.getValues(javaProject, query) + .take(CachingValueProvider.TIMEOUT) + .collectList() + .block(); + } + } + + /** + * Defines a value provider by binding its id to a strategy. + */ + public void def(String id, Function, ValueProviderStrategy> algo) { + registry.put(id, algo); + } + + /** + * Resolve a list of {@link ValueProvider}s to a {@link ValueProviderStrategy}. + *

+ * Essentially this finds the first provider from the list which has a known name + * and uses that to iinstantiate a ValueProviderStrategy. Spring boot assumes that + * a list is provided to allow new providers to be defined that override older ones + * and these are added at the top of the list. Thus an older IDE can continue to + * function using the older provider further down the list whereas newer IDEs will + * use a 'better' one from higher up the list. + */ + public ValueProviderStrategy resolve(List providerDescriptors) { + if (CollectionUtil.hasElements(providerDescriptors)) { + for (ValueProvider descriptor : providerDescriptors) { + Function, ValueProviderStrategy> factory = registry.get(descriptor.getName()); + if (factory!=null) { + Map params = descriptor.getParameters(); + return factory.apply(params); + } + } + } + return null; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/StsValueHint.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/StsValueHint.java new file mode 100644 index 000000000..33a565d4d --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/StsValueHint.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2016-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.metadata.hints; + +import org.springframework.boot.configurationmetadata.Deprecation; +import org.springframework.boot.configurationmetadata.ValueHint; +import org.springframework.ide.vscode.boot.metadata.util.DeprecationUtil; +import org.springframework.ide.vscode.commons.java.IJavaElement; +import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.javadoc.IJavadoc; +import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; +import org.springframework.ide.vscode.commons.util.StringUtil; + +/** + * Sts version of {@link ValueHint} contains similar data, but accomoates + * a html snippet to be computed lazyly for the description. + *

+ * This is meant to support using data pulled from JavaDoc in enums as description. + * This data is a html snippet, whereas the data derived from spring-boot metadata is + * just plain text. + * + * @author Kris De Volder + */ +public class StsValueHint { + + + private final String value; + private final Renderable description; + private final Deprecation deprecation; + + /** + * Create a hint with a textual description. + *

+ * This constructor is private. Use one of the provided + * static 'create' methods instead. + */ + private StsValueHint(String value, Renderable description, Deprecation deprecation) { + this.value = value==null?"null":value.toString(); + Assert.isLegal(!this.value.startsWith("StsValueHint")); + this.description = description; + this.deprecation = deprecation; + } + + /** + * Creates a hint out of an IJavaElement. + */ + public static StsValueHint create(String value, IJavaElement javaElement) { + return new StsValueHint(value, javaDocSnippet(javaElement), DeprecationUtil.extract(javaElement)) { + @Override + public IJavaElement getJavaElement() { + return javaElement; + } + }; + } + + public static StsValueHint create(String value) { + return new StsValueHint(value, Renderables.NO_DESCRIPTION, null); + } + + public static StsValueHint create(ValueHint hint) { + return new StsValueHint(""+hint.getValue(), textSnippet(hint.getDescription()), null); + } + + public static StsValueHint create(IType klass) { + return new StsValueHint(klass.getFullyQualifiedName(), javaDocSnippet(klass), DeprecationUtil.extract(klass)) { + @Override + public IJavaElement getJavaElement() { + return klass; + } + }; + } + + /** + * Create a html snippet from a text snippet. + */ + private static Renderable textSnippet(String description) { + if (StringUtil.hasText(description)) { + return Renderables.text(description); + } + return Renderables.NO_DESCRIPTION; + } + + public String getValue() { + return value; + } + + public Renderable getDescription() { + return description; + } + + private static Renderable javaDocSnippet(IJavaElement je) { + return Renderables.lazy(() -> { + IJavadoc jdoc = je.getJavaDoc(); + if (jdoc != null) { + return jdoc.getRenderable(); + } else { + return Renderables.NO_DESCRIPTION; + } + }); + } + + @Override + public String toString() { + return "StsValueHint("+value+")"; + } + + public Deprecation getDeprecation() { + return deprecation; + } + + public IJavaElement getJavaElement() { + return null; + } + + public StsValueHint prefixWith(String prefix) { + StsValueHint it = this; + return new StsValueHint(prefix+getValue(), description, deprecation) { + @Override + public IJavaElement getJavaElement() { + return it.getJavaElement(); + } + }; + } + + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/ValueHintHoverInfo.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/ValueHintHoverInfo.java new file mode 100644 index 000000000..2ce773fe9 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/hints/ValueHintHoverInfo.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2016-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.metadata.hints; + +import java.util.List; + +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.Renderables; + +import static org.springframework.ide.vscode.commons.util.Renderables.*; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; + +public class ValueHintHoverInfo { + + public static Renderable create(StsValueHint hint) { + Builder builder = ImmutableList.builder(); + builder.add(bold(""+hint.getValue())); + builder.add(paragraph(hint.getDescription())); + return concat(builder.build()); + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/DeprecationUtil.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/DeprecationUtil.java new file mode 100644 index 000000000..9d9cd78b3 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/DeprecationUtil.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2016 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.metadata.util; + +import java.util.Optional; + +import org.springframework.boot.configurationmetadata.Deprecation; +import org.springframework.ide.vscode.commons.java.IAnnotatable; +import org.springframework.ide.vscode.commons.java.IJavaElement; + +import com.google.common.collect.ImmutableSet; + +public class DeprecationUtil { + + private static final ImmutableSet DEPRECATED_ANOT_NAMES = ImmutableSet.of( + "org.springframework.boot.context.properties.DeprecatedConfigurationProperty", + "DeprecatedConfigurationProperty", + "java.lang.Deprecated", + "Deprecated" + ); + + /** + * Extract {@link Deprecation} info from annotations on a {@link IJavaElement} + */ + public static Deprecation extract(IJavaElement je) { + Optional deprecation = Optional.empty(); + if (je instanceof IAnnotatable) { + deprecation = extract((IAnnotatable)je); + } + return deprecation.isPresent() ? deprecation.get() : null; + } + + /** + * Extract {@link Deprecation} info from annotations on a {@link IJavaElement} + */ + private static Optional extract(IAnnotatable m) { + return m.getAnnotations().filter(a -> DEPRECATED_ANOT_NAMES.contains(a.getElementName())).map(a -> { + Deprecation d = new Deprecation(); + a.getMemberValuePairs().forEach(pair -> { + String name = pair.getMemberName(); + if (name.equals("reason")) { + d.setReason((String) pair.getValue()); + } else if (name.equals("replacement")) { + d.setReplacement((String) pair.getValue()); + } + }); + return d; + }).findFirst(); + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/FuzzyMap.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/FuzzyMap.java new file mode 100644 index 000000000..ac551346f --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/FuzzyMap.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2014 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.metadata.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.logging.Logger; + +import org.springframework.ide.vscode.commons.util.FuzzyMatcher; +import org.springframework.ide.vscode.commons.util.StringUtil; + +/** + * A collection of data that can be searched with a simple 'fuzzy' string + * matching algorithm. Clients must override 'getKey' method to define how + * a search 'key' is associated with each data item. + *

+ * The collection can then be searched for items who's key matches + * simple 'fuzzy' patterns. + */ +public abstract class FuzzyMap implements Iterable { + + private static final Logger LOG = Logger.getLogger(FuzzyMap.class.getName()); + + public static class Match { + public double score; + public final E data; + private String pattern; + + public Match(String pattern, double score, E e) { + this.pattern = pattern; + this.score = score; + this.data = e; + } + public static Match getBest(Collection> matches) { + double bestScore = Double.NEGATIVE_INFINITY; + Match best = null; + for (Match match : matches) { + if (match.score>bestScore) { + best = match; + bestScore = match.score; + } + } + return best; + } + + @Override + public String toString() { + return "Match(score="+score+", data="+data+")"; + } + public String getPattern() { + return pattern; + } + } + + @Override + public Iterator iterator() { + return entries.values().iterator(); + } + + private TreeMap entries = new TreeMap(); + + protected abstract String getKey(E entry); + + public void add(E value) { + //This assumes no two entries have the same id. + String key = getKey(value); + E existing = entries.get(key); + if (existing==null) { + entries.put(getKey(value), value); + } else { + LOG.warning(FuzzyMap.class.getName()+": Multiple entries for key "+key+" some entries discarded"); + } + } + + /** + * Search for pattern. A pattern is just a sequence of characters which have to found in + * an entrie's key in the same order as they are in the pattern. + *

+ * Note that returned list doesn't yet have elements sorted according to score (instead they + * are sorted lexicographically thanks to the fact we use a Tree representation). + */ + public List> find(String pattern) { + if ("".equals(pattern)) { + //Special case because + // 1) no need to search. Matches everything + // 2) want to use different way of sorting / scoring. See https://issuetracker.springsource.com/browse/STS-4008 + ArrayList> matches = new ArrayList>(entries.size()); + for (E v : entries.values()) { + matches.add(new Match(pattern, 1.0, v)); + } + return matches; + } else { + //TODO: optimize somehow with a smarter index? (right now searches all map entries sequentially) + ArrayList> matches = new ArrayList>(); + for (Entry e : entries.entrySet()) { + String key = e.getKey(); + double score = FuzzyMatcher.matchScore(pattern, key); + if (score!=0.0) { + matches.add(new Match(pattern, score, e.getValue())); + } + } + return matches; + } + } + + /** + * Searches the index for the longest string which is both + * - a prefix of propertyName + * - a prefix of some key in the map. + * Note: If the map is empty, then this returns null, since + * no string, not even the empty string is a prefix of a + * key in the map. + */ + public String findValidPrefix(String propertyName) { + E best = findLongestCommonPrefixEntry(propertyName); + return best==null?null:StringUtil.commonPrefix(propertyName, getKey(best)); + } + + /** + * Find property with longest common prefix for given key. + */ + public E findLongestCommonPrefixEntry(String propertyName) { + //We can implementation this O(log(n)) because the properties are kept in a TreeMap which is sorted. + //This means that entries with common prefix will occur 'next to eachother' + //The 'best' entry must therefore be either the entry just before or just after + //the property we are searching for. + + Entry ceiln = entries.ceilingEntry(propertyName); + Entry floor = entries.floorEntry(propertyName); + Entry best; + if (floor==null || floor==ceiln) { + best = ceiln; + } else if (ceiln==null) { + best = floor; + } else { + int floorScore = floor==null?0:StringUtil.commonPrefixLength(floor.getKey(), propertyName); + int ceilnScore = ceiln==null?0:StringUtil.commonPrefixLength(ceiln.getKey(), propertyName); + best = floorScore>ceilnScore ? floor : ceiln; + } + return best==null?null:best.getValue(); + } + + /** + * Find an exact match if it exists. + */ + public E get(String id) { + return entries.get(id); + } + + public boolean isEmpty() { + return entries==null || entries.isEmpty(); + } + + public int size() { + return entries.size(); + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/Listener.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/Listener.java new file mode 100644 index 000000000..10aa67fdc --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/Listener.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2014 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.metadata.util; + +/** + * @author Kris De Volder + */ +public interface Listener { + + void changed(T info); + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/ListenerManager.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/ListenerManager.java new file mode 100644 index 000000000..f3921598a --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/metadata/util/ListenerManager.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2014 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.metadata.util; + +import java.util.Arrays; + +import org.springframework.ide.vscode.commons.util.ListenerList; + +public class ListenerManager { + + private ListenerList listeners = new ListenerList<>(ListenerList.IDENTITY); + + public void addListener(T l) { + listeners.add(l); + } + + public void removeListener(T l) { + listeners.remove(l); + } + + @SuppressWarnings("unchecked") + public Iterable getListeners() { + return (Iterable) Arrays.asList(listeners.getListeners()); + } + + + +} diff --git a/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/boot/java/completions/test/ScopeCompletionTest.java b/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/boot/java/completions/test/ScopeCompletionTest.java index 02f21e878..696ae5547 100644 --- a/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/boot/java/completions/test/ScopeCompletionTest.java +++ b/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/boot/java/completions/test/ScopeCompletionTest.java @@ -27,6 +27,7 @@ import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFin import org.springframework.ide.vscode.languageserver.testharness.Editor; import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.ide.vscode.project.harness.PropertyIndexHarness; /** * @author Martin Lippert @@ -36,18 +37,21 @@ public class ScopeCompletionTest { private final JavaProjectFinder javaProjectFinder = (doc) -> getTestProject(); private LanguageServerHarness harness; + private PropertyIndexHarness indexHarness; private IJavaProject testProject; private Editor editor; + @Before public void setup() throws Exception { - testProject = ProjectsHarness.INSTANCE.mavenProject("test-scope-annotation"); + testProject = ProjectsHarness.INSTANCE.mavenProject("test-annotations"); + indexHarness = new PropertyIndexHarness(); harness = new LanguageServerHarness(new Callable() { @Override public BootJavaLanguageServer call() throws Exception { - BootJavaLanguageServer server = new BootJavaLanguageServer(javaProjectFinder); + BootJavaLanguageServer server = new BootJavaLanguageServer(javaProjectFinder, indexHarness.getIndexProvider()); return server; } }) { @@ -155,7 +159,7 @@ public class ScopeCompletionTest { } private void prepareCase(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception { - InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-scope-annotation/src/main/java/org/test/TestScopeCompletion.java"); + InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-annotations/src/main/java/org/test/TestScopeCompletion.java"); String content = IOUtils.toString(resource); content = content.replace(selectedAnnotation, annotationStatementBeforeTest); diff --git a/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/boot/java/completions/test/ValueCompletionTest.java b/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/boot/java/completions/test/ValueCompletionTest.java new file mode 100644 index 000000000..637096e01 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/boot/java/completions/test/ValueCompletionTest.java @@ -0,0 +1,248 @@ +/******************************************************************************* + * 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.completions.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.InputStream; +import java.util.List; +import java.util.concurrent.Callable; + +import org.apache.commons.io.IOUtils; +import org.eclipse.lsp4j.CompletionItem; +import org.junit.Before; +import org.junit.Test; +import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer; +import org.springframework.ide.vscode.boot.java.completions.ValueCompletionProcessor; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.languageserver.testharness.Editor; +import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.ide.vscode.project.harness.PropertyIndexHarness; + +/** + * @author Martin Lippert + */ +public class ValueCompletionTest { + + private final JavaProjectFinder javaProjectFinder = (doc) -> getTestProject(); + + private LanguageServerHarness harness; + private IJavaProject testProject; + + private Editor editor; + + private PropertyIndexHarness indexHarness; + + @Before + public void setup() throws Exception { + testProject = ProjectsHarness.INSTANCE.mavenProject("test-annotations"); + indexHarness = new PropertyIndexHarness(); + + harness = new LanguageServerHarness(new Callable() { + @Override + public BootJavaLanguageServer call() throws Exception { + BootJavaLanguageServer server = new BootJavaLanguageServer(javaProjectFinder, indexHarness.getIndexProvider()); + return server; + } + }) { + @Override + protected String getFileExtension() { + return ".java"; + } + }; + harness.intialize(null); + } + + private IJavaProject getTestProject() { + return testProject; + } + + @Test + public void testPrefixIdentification() { + ValueCompletionProcessor processor = new ValueCompletionProcessor(null); + + assertEquals("pre", processor.identifyPropertyPrefix("pre", 3)); + assertEquals("pre", processor.identifyPropertyPrefix("prefix", 3)); + assertEquals("", processor.identifyPropertyPrefix("", 0)); + assertEquals("pre", processor.identifyPropertyPrefix("$pre", 4)); + + assertEquals("", processor.identifyPropertyPrefix("${pre", 0)); + assertEquals("", processor.identifyPropertyPrefix("${pre", 1)); + assertEquals("", processor.identifyPropertyPrefix("${pre", 2)); + assertEquals("p", processor.identifyPropertyPrefix("${pre", 3)); + assertEquals("pr", processor.identifyPropertyPrefix("${pre", 4)); + } + + @Test + public void testEmptyBracketsCompletion() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(<*>)"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"${data.prop2}\"<*>)", + "@Value(\"${else.prop3}\"<*>)", + "@Value(\"${spring.prop1}\"<*>)"); + } + + @Test + public void testOnlyDollarNoQoutesCompletion() throws Exception { + prepareCase("@Value(\"onField\")", "@Value($<*>)"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"${data.prop2}\"<*>)", + "@Value(\"${else.prop3}\"<*>)", + "@Value(\"${spring.prop1}\"<*>)"); + } + + @Test + public void testOnlyDollarCompletion() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(\"$<*>\")"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"${data.prop2}<*>\")", + "@Value(\"${else.prop3}<*>\")", + "@Value(\"${spring.prop1}<*>\")"); + } + + @Test + public void testDollarWithBracketsCompletion() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(\"${<*>}\")"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"${data.prop2<*>}\")", + "@Value(\"${else.prop3<*>}\")", + "@Value(\"${spring.prop1<*>}\")"); + } + + @Test + public void testEmptyStringLiteralCompletion() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(\"<*>\")"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"${data.prop2}<*>\")", + "@Value(\"${else.prop3}<*>\")", + "@Value(\"${spring.prop1}<*>\")"); + } + + @Test + public void testPlainPrefixCompletion() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(spri<*>)"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"${spring.prop1}\"<*>)"); + } + + @Test + public void testQoutedPrefixCompletion() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(\"spri<*>\")"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"${spring.prop1}<*>\")"); + } + + @Test + public void testRandomSpelExpressionNoCompletion() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(\"#{<*>}\")"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"#{${data.prop2}<*>}\")", + "@Value(\"#{${else.prop3}<*>}\")", + "@Value(\"#{${spring.prop1}<*>}\")"); + } + + @Test + public void testRandomSpelExpressionWithPropertyDollar() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(\"#{345$<*>}\")"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"#{345${data.prop2}<*>}\")", + "@Value(\"#{345${else.prop3}<*>}\")", + "@Value(\"#{345${spring.prop1}<*>}\")"); + } + + @Test + public void testRandomSpelExpressionWithPropertyDollerWithoutClosindBracket() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(\"#{345${<*>}\")"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"#{345${data.prop2}<*>}\")", + "@Value(\"#{345${else.prop3}<*>}\")", + "@Value(\"#{345${spring.prop1}<*>}\")"); + } + + @Test + public void testRandomSpelExpressionWithPropertyDollerWithClosingBracket() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(\"#{345${<*>}}\")"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"#{345${data.prop2<*>}}\")", + "@Value(\"#{345${else.prop3<*>}}\")", + "@Value(\"#{345${spring.prop1<*>}}\")"); + } + + @Test + public void testRandomSpelExpressionWithPropertyPrefixWithoutClosingBracket() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(\"#{345${spri<*>}\")"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"#{345${spring.prop1}<*>}\")"); + } + + @Test + public void testRandomSpelExpressionWithPropertyPrefixWithClosingBracket() throws Exception { + prepareCase("@Value(\"onField\")", "@Value(\"#{345${spri<*>}}\")"); + prepareDefaultIndexData(); + + assertAnnotationCompletions( + "@Value(\"#{345${spring.prop1<*>}}\")"); + } + + private void prepareDefaultIndexData() { + indexHarness.data("spring.prop1", "java.lang.String", null, null); + indexHarness.data("data.prop2", "java.lang.String", null, null); + indexHarness.data("else.prop3", "java.lang.String", null, null); + } + + private void prepareCase(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception { + InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-annotations/src/main/java/org/test/TestValueCompletion.java"); + String content = IOUtils.toString(resource); + + content = content.replace(selectedAnnotation, annotationStatementBeforeTest); + editor = new Editor(harness, content, "java"); + } + + private void assertAnnotationCompletions(String... completedAnnotations) throws Exception { + List completions = editor.getCompletions(); + int i = 0; + for (String expectedCompleted : completedAnnotations) { + Editor clonedEditor = editor.clone(); + clonedEditor.apply(completions.get(i++)); + assertTrue(clonedEditor.getText().contains(expectedCompleted)); + } + + assertEquals(i, completions.size()); + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java b/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java index f7f95cf19..516a0c3d4 100644 --- a/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java +++ b/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/project/harness/ProjectsHarness.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016 Pivotal, Inc. + * Copyright (c) 2016, 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 @@ -29,7 +29,6 @@ import com.google.common.cache.CacheBuilder; * Test projects harness * * @author Alex Boyko - * */ public class ProjectsHarness { @@ -62,94 +61,13 @@ public class ProjectsHarness { } protected Path getProjectPath(String name) throws URISyntaxException, IOException { -// URI sourceLocation = ProjectsHarness.class.getProtectionDomain().getCodeSource().getLocation().toURI(); -// // file:/Users/aboyko/git/sts4/vscode-extensions/commons/project-test-harness/target/project-test-harness-0.0.1-SNAPSHOT.jar -// Path testProjectsPath = Paths.get(sourceLocation).getParent().getParent().resolve("test-projects").resolve(name); -// if (Files.exists(testProjectsPath)) { -// return testProjectsPath; -// } else { -// /* -// * If "test-projects" folder is not found then extract test project -// * from the jar's "test-projects" folder and copy it in the temp -// * folder -// */ - return getProjectPathFromClasspath(name); -// } + return getProjectPathFromClasspath(name); } private Path getProjectPathFromClasspath(String name) throws URISyntaxException, IOException { URI resource = ProjectsHarness.class.getResource("/test-projects/" + name).toURI(); -// if (resource.getScheme().equalsIgnoreCase("jar")) { -// return getProjectPathFromJar(resource); -// } else { - return Paths.get(resource); -// } - } - -// private Path getProjectPathFromJar(URI jar) throws IOException { -// final String[] array = jar.toString().split("!"); -// URI firstHalf = URI.create(array[0]); -// Path tempFolderPath = Paths.get(new File(System.getProperty(MavenCore.JAVA_IO_TMPDIR)).toURI()); -// FileSystem fs = FileSystems.newFileSystem(firstHalf, Collections.emptyMap()); -// try { -// Path path = fs.getPath(array[1]); -// Path projectCopyPath = tempFolderPath.resolve(path.getFileName().toString()); -// if (Files.exists(projectCopyPath)) { -// recursiveDelete(projectCopyPath); -// } -// recursiveCopy(path, tempFolderPath, StandardCopyOption.REPLACE_EXISTING); -// System.out.println("Copied test project to: " + projectCopyPath); -// return projectCopyPath; -// } finally { -// fs.close(); -// } -// } -// -// private static void recursiveCopy(Path source, Path target, CopyOption... options) throws IOException { -// Files.walkFileTree(source, new SimpleFileVisitor() { -// -// Path destination = target; -// -// @Override -// public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { -// destination = destination.resolve(dir.getFileName().toString()); -// Files.copy(dir, destination, options); -// return super.preVisitDirectory(dir, attrs); -// } -// -// @Override -// public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { -// Path newFile = destination.resolve(file.getFileName().toString()); -// Files.copy(file, newFile, options); -// return super.visitFile(file, attrs); -// } -// -// @Override -// public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { -// destination = destination.getParent(); -// return super.postVisitDirectory(dir, exc); -// } -// -// }); -// } -// -// private static void recursiveDelete(Path path) throws IOException { -// Files.walkFileTree(path, new SimpleFileVisitor() { -// -// @Override -// public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { -// Files.delete(file); -// return super.visitFile(file, attrs); -// } -// -// @Override -// public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { -// Files.delete(dir); -// return super.postVisitDirectory(dir, exc); -// } -// -// }); -// } + return Paths.get(resource); + } public MavenJavaProject mavenProject(String name) throws Exception { return (MavenJavaProject) project(ProjectType.MAVEN, name); diff --git a/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/project/harness/PropertyIndexHarness.java b/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/project/harness/PropertyIndexHarness.java new file mode 100644 index 000000000..ab1063e03 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/test/java/org/springframework/ide/vscode/project/harness/PropertyIndexHarness.java @@ -0,0 +1,565 @@ +/******************************************************************************* + * 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.project.harness; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty; +import org.springframework.boot.configurationmetadata.Deprecation; +import org.springframework.boot.configurationmetadata.ValueHint; +import org.springframework.boot.configurationmetadata.ValueProvider; +import org.springframework.ide.vscode.boot.metadata.PropertyInfo; +import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndex; +import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; +import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry; +import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap; +import org.springframework.ide.vscode.commons.java.IClasspath; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.util.text.IDocument; + +/** + * Provides some convenience apis for test code to create / use test data for a SpringPropertyIndex. + */ +public class PropertyIndexHarness { + + private Map datas = new LinkedHashMap<>(); + private ValueProviderRegistry valueProviders = ValueProviderRegistry.getDefault(); + private SpringPropertyIndex index = null; + private IJavaProject testProject = null; + + protected final SpringPropertyIndexProvider indexProvider = new SpringPropertyIndexProvider() { + @Override + public FuzzyMap getIndex(IDocument doc) { + synchronized (PropertyIndexHarness.this) { + if (index==null) { + IClasspath classpath = testProject == null ? null : testProject.getClasspath(); + index = new SpringPropertyIndex(valueProviders, classpath); + for (ConfigurationMetadataProperty propertyInfo : datas.values()) { + index.add(propertyInfo); + } + } + return index; + } + } + }; + + public synchronized void useProject(IJavaProject p) throws Exception { + index = null; + this.testProject = p; + } + + public class ItemConfigurer { + + private ConfigurationMetadataProperty item; + + public ItemConfigurer(ConfigurationMetadataProperty item) { + this.item = item; + } + + /** + * Add a provider with a single parameter. + * @return + */ + public ItemConfigurer provider(String name, String paramName, Object paramValue) { + ValueProvider provider = new ValueProvider(); + provider.setName(name); + provider.getParameters().put(paramName, paramValue); + item.getHints().getValueProviders().add(provider); + return this; + } + + /** + * Add a value hint. If description contains a '.' the dot is used + * to break description into a short and long description. + * @return + */ + public ItemConfigurer valueHint(Object value, String description) { + ValueHint hint = new ValueHint(); + hint.setValue(value); + if (description!=null) { + int dotPos = description.indexOf('.'); + if (dotPos>=0) { + hint.setShortDescription( description.substring(0, dotPos)); + } + hint.setDescription(description); + } + item.getHints().getValueHints().add(hint); + return this; + } + } + + + public synchronized ItemConfigurer data(String id, String type, Object deflt, String description, + String... source + ) { + ConfigurationMetadataProperty item = new ConfigurationMetadataProperty(); + item.setId(id); + item.setDescription(description); + item.setType(type); + item.setDefaultValue(deflt); + index = null; + datas.put(item.getId(), item); + return new ItemConfigurer(item); + } + + public synchronized void keyHints(String id, String... hintValues) { + index = null; + List hints = datas.get(id).getHints().getKeyHints(); + for (String value : hintValues) { + ValueHint hint = new ValueHint(); + hint.setValue(value); + hints.add(hint); + } + } + + public synchronized void valueHints(String id, String... hintValues) { + index = null; + List hints = datas.get(id).getHints().getValueHints(); + for (String value : hintValues) { + ValueHint hint = new ValueHint(); + hint.setValue(value); + hints.add(hint); + } + } + + public synchronized void deprecate(String key, String replacedBy, String reason) { + index = null; + ConfigurationMetadataProperty info = datas.get(key); + Deprecation d = new Deprecation(); + d.setReplacement(replacedBy); + d.setReason(reason); + info.setDeprecation(d); + } + + /** + * Call this method to add some default test data to the Completion engine's index. + * Note that this data is not added automatically, some test may want to use smaller + * test data sets. + */ + public void defaultTestData() { + data("banner.charset", "java.nio.charset.Charset", "UTF-8", "Banner file encoding."); + data("banner.location", "java.lang.String", "classpath:banner.txt", "Banner file location."); + data("debug", "java.lang.Boolean", "false", "Enable debug logs."); + data("flyway.check-location", "java.lang.Boolean", "false", "Check that migration scripts location exists."); + data("flyway.clean-on-validation-error", "java.lang.Boolean", null, null); + data("flyway.enabled", "java.lang.Boolean", "true", "Enable flyway."); + data("flyway.encoding", "java.lang.String", null, null); + data("flyway.ignore-failed-future-migration", "java.lang.Boolean", null, null); + data("flyway.init-description", "java.lang.String", null, null); + data("flyway.init-on-migrate", "java.lang.Boolean", null, null); + data("flyway.init-sqls", "java.util.List", null, "SQL statements to execute to initialize a connection immediately after obtaining\n it."); + data("flyway.init-version", "org.flywaydb.core.api.MigrationVersion", null, null); + data("flyway.locations", "java.util.List", null, "Locations of migrations scripts."); + data("flyway.out-of-order", "java.lang.Boolean", null, null); + data("flyway.password", "java.lang.String", null, "Login password of the database to migrate."); + data("flyway.placeholder-prefix", "java.lang.String", null, null); + data("flyway.placeholders", "java.util.Map", null, null); + data("flyway.placeholder-suffix", "java.lang.String", null, null); + data("flyway.schemas", "java.lang.String[]", null, null); + data("flyway.sql-migration-prefix", "java.lang.String", null, null); + data("flyway.sql-migration-separator", "java.lang.String", null, null); + data("flyway.sql-migration-suffix", "java.lang.String", null, null); + data("flyway.table", "java.lang.String", null, null); + data("flyway.target", "org.flywaydb.core.api.MigrationVersion", null, null); + data("flyway.url", "java.lang.String", null, "JDBC url of the database to migrate. If not set, the primary configured data source\n is used."); + data("flyway.user", "java.lang.String", null, "Login user of the database to migrate."); + data("flyway.validate-on-migrate", "java.lang.Boolean", null, null); + data("http.mappers.json-pretty-print", "java.lang.Boolean", null, "Enable json pretty print."); + data("http.mappers.json-sort-keys", "java.lang.Boolean", null, "Enable key sorting."); + data("liquibase.change-log", "java.lang.String", "classpath:/db/changelog/db.changelog-master.yaml", "Change log configuration path."); + data("liquibase.check-change-log-location", "java.lang.Boolean", "true", "Check the change log location exists."); + data("liquibase.contexts", "java.lang.String", null, "Comma-separated list of runtime contexts to use."); + data("liquibase.default-schema", "java.lang.String", null, "Default database schema."); + data("liquibase.drop-first", "java.lang.Boolean", "false", "Drop the database schema first."); + data("liquibase.enabled", "java.lang.Boolean", "true", "Enable liquibase support."); + data("liquibase.password", "java.lang.String", null, "Login password of the database to migrate."); + data("liquibase.url", "java.lang.String", null, "JDBC url of the database to migrate. If not set, the primary configured data source\n is used."); + data("liquibase.user", "java.lang.String", null, "Login user of the database to migrate."); + data("logging.config", "java.lang.String", null, "Location of the logging configuration file."); + data("logging.file", "java.lang.String", null, "Log file name."); + data("logging.level", "java.util.Map", null, "Log levels severity mapping. Use 'root' for the root logger."); + data("logging.path", "java.lang.String", null, "Location of the log file."); + data("multipart.file-size-threshold", "java.lang.String", "0", "Threshold after which files will be written to disk. Values can use the suffixed\n \"MB\" or \"KB\" to indicate a Megabyte or Kilobyte size."); + data("multipart.location", "java.lang.String", null, "Intermediate location of uploaded files."); + data("multipart.max-file-size", "java.lang.String", "1Mb", "Max file size. Values can use the suffixed \"MB\" or \"KB\" to indicate a Megabyte or\n Kilobyte size."); + data("multipart.max-request-size", "java.lang.String", "10Mb", "Max request size. Values can use the suffixed \"MB\" or \"KB\" to indicate a Megabyte\n or Kilobyte size."); + data("security.basic.enabled", "java.lang.Boolean", "true", "Enable basic authentication."); + data("security.basic.path", "java.lang.String[]", "[Ljava.lang.Object;@7abd0056", "Comma-separated list of paths to secure."); + data("security.basic.realm", "java.lang.String", "Spring", "HTTP basic realm name."); + data("security.enable-csrf", "java.lang.Boolean", "false", "Enable Cross Site Request Forgery support."); + data("security.filter-order", "java.lang.Integer", "0", "Security filter chain order."); + data("security.headers.cache", "java.lang.Boolean", "false", "Enable cache control HTTP headers."); + data("security.headers.content-type", "java.lang.Boolean", "false", "Enable \"X-Content-Type-Options\" header."); + data("security.headers.frame", "java.lang.Boolean", "false", "Enable \"X-Frame-Options\" header."); + data("security.headers.hsts", "org.springframework.boot.autoconfigure.security.SecurityProperties$Headers$HSTS", null, "HTTP Strict Transport Security (HSTS) mode (none, domain, all)."); + data("security.headers.xss", "java.lang.Boolean", "false", "Enable cross site scripting (XSS) protection."); + data("security.ignored", "java.util.List", null, "Comma-separated list of paths to exclude from the default secured paths."); + data("security.require-ssl", "java.lang.Boolean", "false", "Enable secure channel for all requests."); + data("security.sessions", "org.springframework.security.config.http.SessionCreationPolicy", null, "Session creation policy (always, never, if_required, stateless)."); + data("security.user.name", "java.lang.String", "user", "Default user name."); + data("security.user.password", "java.lang.String", null, "Password for the default user name."); + data("security.user.role", "java.util.List", null, "Granted roles for the default user name."); + data("server.address", "java.net.InetAddress", null, "Network address to which the server should bind to."); + data("server.context-parameters", "java.util.Map", null, "ServletContext parameters."); + data("server.context-path", "java.lang.String", null, "Context path of the application."); + data("server.port", "java.lang.Integer", null, "Server HTTP port."); + data("server.servlet-path", "java.lang.String", "/", "Path of the main dispatcher servlet."); + data("server.session-timeout", "java.lang.Integer", null, "Session timeout in seconds."); + data("server.ssl.ciphers", "java.lang.String[]", null, null); + data("server.ssl.client-auth", "org.springframework.boot.context.embedded.Ssl$ClientAuth", null, null); + data("server.ssl.key-alias", "java.lang.String", null, null); + data("server.ssl.key-password", "java.lang.String", null, null); + data("server.ssl.key-store", "java.lang.String", null, null); + data("server.ssl.key-store-password", "java.lang.String", null, null); + data("server.ssl.key-store-provider", "java.lang.String", null, null); + data("server.ssl.key-store-type", "java.lang.String", null, null); + data("server.ssl.protocol", "java.lang.String", null, null); + data("server.ssl.trust-store", "java.lang.String", null, null); + data("server.ssl.trust-store-password", "java.lang.String", null, null); + data("server.ssl.trust-store-provider", "java.lang.String", null, null); + data("server.ssl.trust-store-type", "java.lang.String", null, null); + data("server.tomcat.access-log-enabled", "java.lang.Boolean", "false", "Enable access log."); + data("server.tomcat.access-log-pattern", "java.lang.String", null, "Format pattern for access logs."); + data("server.tomcat.background-processor-delay", "java.lang.Integer", "30", "Delay in seconds between the invocation of backgroundProcess methods."); + data("server.tomcat.basedir", "java.io.File", null, "Tomcat base directory. If not specified a temporary directory will be used."); + data("server.tomcat.internal-proxies", "java.lang.String", "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|192\\.168\\.\\d{1,3}\\.\\d{1,3}|169\\.254\\.\\d{1,3}\\.\\d{1,3}|127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}", "Regular expression that matches proxies that are to be trusted."); + data("server.tomcat.max-http-header-size", "java.lang.Integer", "0", "Maximum size in bytes of the HTTP message header."); + data("server.tomcat.max-threads", "java.lang.Integer", "0", "Maximum amount of worker threads."); + data("server.tomcat.port-header", "java.lang.String", null, "Name of the HTTP header used to override the original port value."); + data("server.tomcat.protocol-header", "java.lang.String", null, "Header that holds the incoming protocol, usually named \"X-Forwarded-Proto\".\n Configured as a RemoteIpValve only if remoteIpHeader is also set."); + data("server.tomcat.remote-ip-header", "java.lang.String", null, "Name of the http header from which the remote ip is extracted. Configured as a\n RemoteIpValve only if remoteIpHeader is also set."); + data("server.tomcat.uri-encoding", "java.lang.String", null, "Character encoding to use to decode the URI."); + data("server.undertow.buffer-size", "java.lang.Integer", null, "Size of each buffer in bytes."); + data("server.undertow.buffers-per-region", "java.lang.Integer", null, "Number of buffer per region."); + data("server.undertow.direct-buffers", "java.lang.Boolean", null, null); + data("server.undertow.io-threads", "java.lang.Integer", null, "Number of I/O threads to create for the worker."); + data("server.undertow.worker-threads", "java.lang.Integer", null, "Number of worker threads."); + data("spring.activemq.broker-url", "java.lang.String", null, "URL of the ActiveMQ broker. Auto-generated by default."); + data("spring.activemq.in-memory", "java.lang.Boolean", "true", "Specify if the default broker URL should be in memory. Ignored if an explicit\n broker has been specified."); + data("spring.activemq.password", "java.lang.String", null, "Login password of the broker."); + data("spring.activemq.pooled", "java.lang.Boolean", "false", "Specify if a PooledConnectionFactory should be created instead of a regular\n ConnectionFactory."); + data("spring.activemq.user", "java.lang.String", null, "Login user of the broker."); + data("spring.aop.auto", "java.lang.Boolean", "true", "Add @EnableAspectJAutoProxy."); + data("spring.aop.proxy-target-class", "java.lang.Boolean", "false", "Whether subclass-based (CGLIB) proxies are to be created (true) as opposed to standard Java interface-based proxies (false)."); + data("spring.application.index", "java.lang.Integer", null, "Application index."); + data("spring.application.name", "java.lang.String", null, "Application name."); + data("spring.batch.initializer.enabled", "java.lang.Boolean", "true", "Create the required batch tables on startup if necessary."); + data("spring.batch.job.enabled", "java.lang.Boolean", "true", "Execute all Spring Batch jobs in the context on startup."); + data("spring.batch.job.names", "java.lang.String", "", "Comma-separated list of job names to execute on startup. By default, all Jobs\n found in the context are executed."); + data("spring.batch.schema", "java.lang.String", "classpath:org/springframework/batch/core/schema-@@platform@@.sql", "Path to the SQL file to use to initialize the database schema."); + data("spring.config.location", "java.lang.String", null, "Config file locations."); + data("spring.config.name", "java.lang.String", "application", "Config file name."); + data("spring.dao.exceptiontranslation.enabled", "java.lang.Boolean", "true", "Enable the PersistenceExceptionTranslationPostProcessor."); + data("spring.data.elasticsearch.cluster-name", "java.lang.String", "elasticsearch", "Elasticsearch cluster name."); + data("spring.data.elasticsearch.cluster-nodes", "java.lang.String", null, "Comma-separated list of cluster node addresses. If not specified, starts a client\n node."); + data("spring.data.elasticsearch.repositories.enabled", "java.lang.Boolean", "true", "Enable Elasticsearch repositories."); + data("spring.data.jpa.repositories.enabled", "java.lang.Boolean", "true", "Enable JPA repositories."); + data("spring.data.mongodb.authentication-database", "java.lang.String", null, "Authentication database name."); + data("spring.data.mongodb.database", "java.lang.String", null, "Database name."); + data("spring.data.mongodb.grid-fs-database", "java.lang.String", null, "GridFS database name."); + data("spring.data.mongodb.host", "java.lang.String", null, "Mongo server host."); + data("spring.data.mongodb.password", "char[]", null, "Login password of the mongo server."); + data("spring.data.mongodb.port", "java.lang.Integer", null, "Mongo server port."); + data("spring.data.mongodb.repositories.enabled", "java.lang.Boolean", "true", "Enable Mongo repositories."); + data("spring.data.mongodb.uri", "java.lang.String", "mongodb://localhost/test", "Mmongo database URI. When set, host and port are ignored."); + data("spring.data.mongodb.username", "java.lang.String", null, "Login user of the mongo server."); + data("spring.data.rest.base-uri", "java.net.URI", null, null); + data("spring.data.rest.default-page-size", "java.lang.Integer", null, null); + data("spring.data.rest.limit-param-name", "java.lang.String", null, null); + data("spring.data.rest.max-page-size", "java.lang.Integer", null, null); + data("spring.data.rest.page-param-name", "java.lang.String", null, null); + data("spring.data.rest.return-body-on-create", "java.lang.Boolean", null, null); + data("spring.data.rest.return-body-on-update", "java.lang.Boolean", null, null); + data("spring.data.rest.sort-param-name", "java.lang.String", null, null); + data("spring.data.solr.host", "java.lang.String", "http://127.0.0.1:8983/solr", "Solr host. Ignored if \"zk-host\" is set."); + data("spring.data.solr.repositories.enabled", "java.lang.Boolean", "true", "Enable Solr repositories."); + data("spring.data.solr.zk-host", "java.lang.String", null, "ZooKeeper host address in the form HOST:PORT."); + data("spring.datasource.abandon-when-percentage-full", "java.lang.Integer", null, null); + data("spring.datasource.access-to-underlying-connection-allowed", "java.lang.Boolean", null, null); + data("spring.datasource.alternate-username-allowed", "java.lang.Boolean", null, null); + data("spring.datasource.auto-commit", "java.lang.Boolean", null, null); + data("spring.datasource.catalog", "java.lang.String", null, null); + data("spring.datasource.commit-on-return", "java.lang.Boolean", null, null); + data("spring.datasource.connection-customizer-class-name", "java.lang.String", null, null); + data("spring.datasource.connection-init-sql", "java.lang.String", null, null); + data("spring.datasource.connection-init-sqls", "java.util.Collection", null, null); + data("spring.datasource.connection-properties", "java.lang.String", null, null); + data("spring.datasource.connection-test-query", "java.lang.String", null, null); + data("spring.datasource.connection-timeout", "java.lang.Long", null, null); + data("spring.datasource.continue-on-error", "java.lang.Boolean", "false", "Do not stop if an error occurs while initializing the database."); + data("spring.datasource.data", "java.lang.String", null, "Data (DML) script resource reference."); + data("spring.datasource.data-source-class-name", "java.lang.String", null, null); + data("spring.datasource.data-source", "java.lang.Object", null, null); + data("spring.datasource.data-source-j-n-d-i", "java.lang.String", null, null); + data("spring.datasource.data-source-properties", "java.util.Properties", null, null); + data("spring.datasource.db-properties", "java.util.Properties", null, null); + data("spring.datasource.default-auto-commit", "java.lang.Boolean", null, null); + data("spring.datasource.default-catalog", "java.lang.String", null, null); + data("spring.datasource.default-read-only", "java.lang.Boolean", null, null); + data("spring.datasource.default-transaction-isolation", "java.lang.Integer", null, null); + data("spring.datasource.driver-class-name", "java.lang.String", null, "Fully qualified name of the JDBC driver. Auto-detected based on the URL by default."); + data("spring.datasource.fair-queue", "java.lang.Boolean", null, null); + data("spring.datasource.idle-timeout", "java.lang.Long", null, null); + data("spring.datasource.ignore-exception-on-pre-load", "java.lang.Boolean", null, null); + data("spring.datasource.initialization-fail-fast", "java.lang.Boolean", null, null); + data("spring.datasource.initialize", "java.lang.Boolean", "true", "Populate the database using 'data.sql'."); + data("spring.datasource.initial-size", "java.lang.Integer", null, null); + data("spring.datasource.init-s-q-l", "java.lang.String", null, null); + data("spring.datasource.isolate-internal-queries", "java.lang.Boolean", null, null); + data("spring.datasource.jdbc4-connection-test", "java.lang.Boolean", null, null); + data("spring.datasource.jdbc-interceptors", "java.lang.String", null, null); + data("spring.datasource.jdbc-url", "java.lang.String", null, null); + data("spring.datasource.jmx-enabled", "java.lang.Boolean", "false", "Enable JMX support (if provided by the underlying pool)."); + data("spring.datasource.jndi-name", "java.lang.String", null, "JNDI location of the datasource. Class, url, username & password are ignored when\n set."); + data("spring.datasource.leak-detection-threshold", "java.lang.Long", null, null); + data("spring.datasource.log-abandoned", "java.lang.Boolean", null, null); + data("spring.datasource.login-timeout", "java.lang.Integer", null, null); + data("spring.datasource.log-validation-errors", "java.lang.Boolean", null, null); + data("spring.datasource.max-active", "java.lang.Integer", null, null); + data("spring.datasource.max-age", "java.lang.Long", null, null); + data("spring.datasource.max-idle", "java.lang.Integer", null, null); + data("spring.datasource.maximum-pool-size", "java.lang.Integer", null, null); + data("spring.datasource.max-lifetime", "java.lang.Long", null, null); + data("spring.datasource.max-open-prepared-statements", "java.lang.Integer", null, null); + data("spring.datasource.max-wait", "java.lang.Integer", null, null); + data("spring.datasource.metric-registry", "java.lang.Object", null, null); + data("spring.datasource.min-evictable-idle-time-millis", "java.lang.Integer", null, null); + data("spring.datasource.min-idle", "java.lang.Integer", null, null); + data("spring.datasource.minimum-idle", "java.lang.Integer", null, null); + data("spring.datasource.name", "java.lang.String", null, null); + data("spring.datasource.num-tests-per-eviction-run", "java.lang.Integer", null, null); + data("spring.datasource.password", "java.lang.String", null, "Login password of the database."); + data("spring.datasource.platform", "java.lang.String", "all", "Platform to use in the schema resource (schema-${platform}.sql)."); + data("spring.datasource.pool-name", "java.lang.String", null, null); + data("spring.datasource.pool-prepared-statements", "java.lang.Boolean", null, null); + data("spring.datasource.propagate-interrupt-state", "java.lang.Boolean", null, null); + data("spring.datasource.read-only", "java.lang.Boolean", null, null); + data("spring.datasource.register-mbeans", "java.lang.Boolean", null, null); + data("spring.datasource.remove-abandoned", "java.lang.Boolean", null, null); + data("spring.datasource.remove-abandoned-timeout", "java.lang.Integer", null, null); + data("spring.datasource.rollback-on-return", "java.lang.Boolean", null, null); + data("spring.datasource.schema", "java.lang.String", null, "Schema (DDL) script resource reference."); + data("spring.datasource.separator", "java.lang.String", ";", "Statement separator in SQL initialization scripts."); + data("spring.datasource.sql-script-encoding", "java.lang.String", null, "SQL scripts encoding."); + data("spring.datasource.suspect-timeout", "java.lang.Integer", null, null); + data("spring.datasource.test-on-borrow", "java.lang.Boolean", null, null); + data("spring.datasource.test-on-connect", "java.lang.Boolean", null, null); + data("spring.datasource.test-on-return", "java.lang.Boolean", null, null); + data("spring.datasource.test-while-idle", "java.lang.Boolean", null, null); + data("spring.datasource.time-between-eviction-runs-millis", "java.lang.Integer", null, null); + data("spring.datasource.transaction-isolation", "java.lang.String", null, null); + data("spring.datasource.url", "java.lang.String", null, "JDBC url of the database."); + data("spring.datasource.use-disposable-connection-facade", "java.lang.Boolean", null, null); + data("spring.datasource.use-equals", "java.lang.Boolean", null, null); + data("spring.datasource.use-lock", "java.lang.Boolean", null, null); + data("spring.datasource.username", "java.lang.String", null, "Login user of the database."); + data("spring.datasource.validation-interval", "java.lang.Long", null, null); + data("spring.datasource.validation-query", "java.lang.String", null, null); + data("spring.datasource.validation-query-timeout", "java.lang.Integer", null, null); + data("spring.datasource.validator-class-name", "java.lang.String", null, null); + data("spring.datasource.xa.data-source-class-name", "java.lang.String", null, "XA datasource fully qualified name."); + data("spring.datasource.xa.properties", "java.util.Map", null, "Properties to pass to the XA data source."); + data("spring.freemarker.allow-request-override", "java.lang.Boolean", null, "Set whether HttpServletRequest attributes are allowed to override (hide) controller\n generated model attributes of the same name."); + data("spring.freemarker.cache", "java.lang.Boolean", null, "Enable template caching."); + data("spring.freemarker.char-set", "java.lang.String", null, null); + data("spring.freemarker.charset", "java.lang.String", null, "Template encoding."); + data("spring.freemarker.check-template-location", "java.lang.Boolean", null, "Check that the templates location exists."); + data("spring.freemarker.content-type", "java.lang.String", null, "Content-Type value."); + data("spring.freemarker.enabled", "java.lang.Boolean", null, "Enable MVC view resolution for this technology."); + data("spring.freemarker.expose-request-attributes", "java.lang.Boolean", null, "Set whether all request attributes should be added to the model prior to merging\n with the template."); + data("spring.freemarker.expose-session-attributes", "java.lang.Boolean", null, "Set whether all HttpSession attributes should be added to the model prior to\n merging with the template."); + data("spring.freemarker.expose-spring-macro-helpers", "java.lang.Boolean", null, "Set whether to expose a RequestContext for use by Spring's macro library, under the\n name \"springMacroRequestContext\"."); + data("spring.freemarker.prefix", "java.lang.String", null, "Prefix that gets prepended to view names when building a URL."); + data("spring.freemarker.request-context-attribute", "java.lang.String", null, "Name of the RequestContext attribute for all views."); + data("spring.freemarker.settings", "java.util.Map", null, "Well-known FreeMarker keys which will be passed to FreeMarker's Configuration."); + data("spring.freemarker.suffix", "java.lang.String", null, "Suffix that gets appended to view names when building a URL."); + data("spring.freemarker.template-loader-path", "java.lang.String[]", new String[] {"snuzzle" ,"buggles"}, "Comma-separated list of template paths."); + data("spring.freemarker.view-names", "java.lang.String[]", null, "White list of view names that can be resolved."); + data("spring.groovy.template.cache", "java.lang.Boolean", null, "Enable template caching."); + data("spring.groovy.template.char-set", "java.lang.String", null, null); + data("spring.groovy.template.charset", "java.lang.String", null, "Template encoding."); + data("spring.groovy.template.check-template-location", "java.lang.Boolean", null, "Check that the templates location exists."); + data("spring.groovy.template.configuration.auto-escape", "java.lang.Boolean", null, null); + data("spring.groovy.template.configuration.auto-indent", "java.lang.Boolean", null, null); + data("spring.groovy.template.configuration.auto-indent-string", "java.lang.String", null, null); + data("spring.groovy.template.configuration.auto-new-line", "java.lang.Boolean", null, null); + data("spring.groovy.template.configuration.base-template-class", "java.lang.Class", null, null); + data("spring.groovy.template.configuration.cache-templates", "java.lang.Boolean", null, null); + data("spring.groovy.template.configuration.declaration-encoding", "java.lang.String", null, null); + data("spring.groovy.template.configuration.expand-empty-elements", "java.lang.Boolean", null, null); + data("spring.groovy.template.configuration", "java.util.Map", null, "Configuration to pass to TemplateConfiguration."); + data("spring.groovy.template.configuration.locale", "java.util.Locale", null, null); + data("spring.groovy.template.configuration.new-line-string", "java.lang.String", null, null); + data("spring.groovy.template.configuration.resource-loader-path", "java.lang.String", null, null); + data("spring.groovy.template.configuration.use-double-quotes", "java.lang.Boolean", null, null); + data("spring.groovy.template.content-type", "java.lang.String", null, "Content-Type value."); + data("spring.groovy.template.enabled", "java.lang.Boolean", null, "Enable MVC view resolution for this technology."); + data("spring.groovy.template.prefix", "java.lang.String", "classpath:/templates/", "Prefix that gets prepended to view names when building a URL."); + data("spring.groovy.template.suffix", "java.lang.String", ".tpl", "Suffix that gets appended to view names when building a URL."); + data("spring.groovy.template.view-names", "java.lang.String[]", null, "White list of view names that can be resolved."); + data("spring.hornetq.embedded.cluster-password", "java.lang.String", null, "Cluster password. Randomly generated on startup by default"); + data("spring.hornetq.embedded.data-directory", "java.lang.String", null, "Journal file directory. Not necessary if persistence is turned off."); + data("spring.hornetq.embedded.enabled", "java.lang.Boolean", "true", "Enable embedded mode if the HornetQ server APIs are available."); + data("spring.hornetq.embedded.persistent", "java.lang.Boolean", "false", "Enable persistent store."); + data("spring.hornetq.embedded.queues", "java.lang.String[]", "[Ljava.lang.Object;@2f5ce114", "Comma-separate list of queues to create on startup."); + data("spring.hornetq.embedded.server-id", "java.lang.Integer", "0", "Server id. By default, an auto-incremented counter is used."); + data("spring.hornetq.embedded.topics", "java.lang.String[]", "[Ljava.lang.Object;@6272137a", "Comma-separate list of topics to create on startup."); + data("spring.hornetq.host", "java.lang.String", "localhost", "HornetQ broker host."); + data("spring.hornetq.mode", "org.springframework.boot.autoconfigure.jms.hornetq.HornetQMode", null, "HornetQ deployment mode, auto-detected by default. Can be explicitly set to\n \"native\" or \"embedded\"."); + data("spring.hornetq.port", "java.lang.Integer", "5445", "HornetQ broker port."); + data("spring.http.encoding.charset", "java.nio.charset.Charset", null, "Charset of HTTP requests and responses. Added to the \"Content-Type\" header if not\n set explicitly."); + data("spring.http.encoding.enabled", "java.lang.Boolean", "true", "Enable http encoding support."); + data("spring.http.encoding.force", "java.lang.Boolean", "true", "Force the encoding to the configured charset on HTTP requests and responses."); + data("spring.jackson.date-format", "java.lang.String", null, "Date format string (yyyy-MM-dd HH:mm:ss), or a fully-qualified date format class\n name."); + data("spring.jackson.deserialization", "java.util.Map", null, "Jackson on/off features that affect the way Java objects are deserialized."); + data("spring.jackson.generator", "java.util.Map", null, "Jackson on/off features for generators."); + data("spring.jackson.mapper", "java.util.Map", null, "Jackson general purpose on/off features."); + data("spring.jackson.parser", "java.util.Map", null, "Jackson on/off features for parsers."); + data("spring.jackson.property-naming-strategy", "java.lang.String", null, "One of the constants on Jackson's PropertyNamingStrategy\n (CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES). Can also be a fully-qualified class\n name of a PropertyNamingStrategy subclass."); + data("spring.jackson.serialization", "java.util.Map", null, "Jackson on/off features that affect the way Java objects are serialized."); + data("spring.jersey.filter.order", "java.lang.Integer", "0", "Jersey filter chain order."); + data("spring.jersey.init", "java.util.Map", null, "Init parameters to pass to Jersey."); + data("spring.jersey.type", "org.springframework.boot.autoconfigure.jersey.JerseyProperties$Type", null, "Jersey integration type. Can be either \"servlet\" or \"filter\"."); + data("spring.jms.jndi-name", "java.lang.String", null, "Connection factory JNDI name. When set, takes precedence to others connection\n factory auto-configurations."); + data("spring.jms.pub-sub-domain", "java.lang.Boolean", "false", "Specify if the default destination type is topic."); + data("spring.jmx.enabled", "java.lang.Boolean", "true", "Expose management beans to the JMX domain."); + data("spring.jpa.database", "org.springframework.orm.jpa.vendor.Database", null, "Target database to operate on, auto-detected by default. Can be alternatively set\n using the \"databasePlatform\" property."); + data("spring.jpa.database-platform", "java.lang.String", null, "Name of the target database to operate on, auto-detected by default. Can be\n alternatively set using the \"Database\" enum."); + data("spring.jpa.generate-ddl", "java.lang.Boolean", "false", "Initialize the schema on startup."); + data("spring.jpa.hibernate.ddl-auto", "java.lang.String", null, "DDL mode (\"none\", \"validate\", \"update\", \"create\", \"create-drop\"). This is\n actually a shortcut for the \"hibernate.hbm2ddl.auto\" property. Default to\n \"create-drop\" when using an embedded database, \"none\" otherwise."); + data("spring.jpa.hibernate.naming-strategy", "java.lang.Class", null, "Naming strategy fully qualified name."); + data("spring.jpa.open-in-view", "java.lang.Boolean", "true", "Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the thread for the entire processing of the request."); + data("spring.jpa.properties", "java.util.Map", null, "Additional native properties to set on the JPA provider."); + data("spring.jpa.show-sql", "java.lang.Boolean", "false", "Enable logging of SQL statements."); + data("spring.jta.allow-multiple-lrc", "java.lang.Boolean", null, null); + data("spring.jta.asynchronous2-pc", "java.lang.Boolean", null, null); + data("spring.jta.background-recovery-interval", "java.lang.Integer", null, null); + data("spring.jta.background-recovery-interval-seconds", "java.lang.Integer", null, null); + data("spring.jta.current-node-only-recovery", "java.lang.Boolean", null, null); + data("spring.jta.debug-zero-resource-transaction", "java.lang.Boolean", null, null); + data("spring.jta.default-transaction-timeout", "java.lang.Integer", null, null); + data("spring.jta.disable-jmx", "java.lang.Boolean", null, null); + data("spring.jta.enabled", "java.lang.Boolean", "true", "Enable JTA support."); + data("spring.jta.exception-analyzer", "java.lang.String", null, null); + data("spring.jta.filter-log-status", "java.lang.Boolean", null, null); + data("spring.jta.force-batching-enabled", "java.lang.Boolean", null, null); + data("spring.jta.forced-write-enabled", "java.lang.Boolean", null, null); + data("spring.jta.graceful-shutdown-interval", "java.lang.Integer", null, null); + data("spring.jta.jndi-transaction-synchronization-registry-name", "java.lang.String", null, null); + data("spring.jta.jndi-user-transaction-name", "java.lang.String", null, null); + data("spring.jta.journal", "java.lang.String", null, null); + data("spring.jta.log-dir", "java.lang.String", null, "Transaction logs directory."); + data("spring.jta.log-part1-filename", "java.lang.String", null, null); + data("spring.jta.log-part2-filename", "java.lang.String", null, null); + data("spring.jta.max-log-size-in-mb", "java.lang.Integer", null, null); + data("spring.jta.resource-configuration-filename", "java.lang.String", null, null); + data("spring.jta.server-id", "java.lang.String", null, null); + data("spring.jta.skip-corrupted-logs", "java.lang.Boolean", null, null); + data("spring.jta.transaction-manager-id", "java.lang.String", null, "Transaction manager unique identifier."); + data("spring.jta.warn-about-zero-resource-transaction", "java.lang.Boolean", null, null); + data("spring.mail.default-encoding", "java.lang.String", "UTF-8", "Default MimeMessage encoding."); + data("spring.mail.host", "java.lang.String", null, "SMTP server host."); + data("spring.mail.password", "java.lang.String", null, "Login password of the SMTP server."); + data("spring.mail.port", "java.lang.Integer", null, "SMTP server port."); + data("spring.mail.properties", "java.util.Map", null, "Additional JavaMail session properties."); + data("spring.mail.username", "java.lang.String", null, "Login user of the SMTP server."); + data("spring.main.show-banner", "java.lang.Boolean", "true", "Display the banner when the application runs."); + data("spring.main.sources", "java.util.Set", null, "Sources (class name, package name or XML resource location) used to create the ApplicationContext."); + data("spring.main.web-environment", "java.lang.Boolean", null, "Run the application in a web environment (auto-detected by default)."); + data("spring.mandatory-file-encoding", "java.lang.String", null, "Expected character encoding the application must use."); + data("spring.messages.basename", "java.lang.String", "messages", "Comma-separated list of basenames, each following the ResourceBundle convention.\n Essentially a fully-qualified classpath location. If it doesn't contain a package\n qualifier (such as \"org.mypackage\"), it will be resolved from the classpath root."); + data("spring.messages.cache-seconds", "java.lang.Integer", "-1", "Loaded resource bundle files cache expiration, in seconds. When set to -1, bundles\n are cached forever."); + data("spring.messages.encoding", "java.lang.String", "utf-8", "Message bundles encoding."); + data("spring.mobile.devicedelegatingviewresolver.enabled", "java.lang.Boolean", "false", "Enable device view resolver."); + data("spring.mobile.devicedelegatingviewresolver.mobile-prefix", "java.lang.String", "mobile/", "Prefix that gets prepended to view names for mobile devices."); + data("spring.mobile.devicedelegatingviewresolver.mobile-suffix", "java.lang.String", "", "Suffix that gets appended to view names for mobile devices."); + data("spring.mobile.devicedelegatingviewresolver.normal-prefix", "java.lang.String", "", "Prefix that gets prepended to view names for normal devices."); + data("spring.mobile.devicedelegatingviewresolver.normal-suffix", "java.lang.String", "", "Suffix that gets appended to view names for normal devices."); + data("spring.mobile.devicedelegatingviewresolver.tablet-prefix", "java.lang.String", "tablet/", "Prefix that gets prepended to view names for tablet devices."); + data("spring.mobile.devicedelegatingviewresolver.tablet-suffix", "java.lang.String", "", "Suffix that gets appended to view names for tablet devices."); + data("spring.mobile.sitepreference.enabled", "java.lang.Boolean", "true", "Enable SitePreferenceHandler."); + data("spring.mvc.date-format", "java.lang.String", null, "Date format to use (e.g. dd/MM/yyyy)"); + data("spring.mvc.ignore-default-model-on-redirect", "java.lang.Boolean", "true", "If the the content of the \"default\" model should be ignored during redirect\n scenarios."); + data("spring.mvc.locale", "java.lang.String", null, "Locale to use."); + data("spring.mvc.message-codes-resolver-format", "org.springframework.validation.DefaultMessageCodesResolver$Format", null, "Formatting strategy for message codes (PREFIX_ERROR_CODE, POSTFIX_ERROR_CODE)."); + data("spring.profiles.active", "java.lang.String", null, "Comma-separated list of active profiles. Can be overridden by a command line switch."); + data("spring.profiles.include", "java.lang.String", null, "Unconditionally activate the specified comma separated profiles."); + data("spring.rabbitmq.addresses", "java.lang.String", null, "Comma-separated list of addresses to which the client should connect to."); + data("spring.rabbitmq.dynamic", "java.lang.Boolean", "true", "Create an AmqpAdmin bean."); + data("spring.rabbitmq.host", "java.lang.String", "localhost", "RabbitMQ host."); + data("spring.rabbitmq.password", "java.lang.String", null, "Login to authenticate against the broker."); + data("spring.rabbitmq.port", "java.lang.Integer", "5672", "RabbitMQ port."); + data("spring.rabbitmq.username", "java.lang.String", null, "Login user to authenticate to the broker."); + data("spring.rabbitmq.virtual-host", "java.lang.String", null, "Virtual host to use when connecting to the broker."); + data("spring.redis.database", "java.lang.Integer", "0", "Database index used by the connection factory."); + data("spring.redis.host", "java.lang.String", "localhost", "Redis server host."); + data("spring.redis.password", "java.lang.String", null, "Login password of the redis server."); + data("spring.redis.pool.max-active", "java.lang.Integer", "8", "Max number of connections that can be allocated by the pool at a given time.\n Use a negative value for no limit."); + data("spring.redis.pool.max-idle", "java.lang.Integer", "8", "Max number of \"idle\" connections in the pool. Use a negative value to indicate\n an unlimited number of idle connections."); + data("spring.redis.pool.max-wait", "java.lang.Integer", "-1", "Maximum amount of time (in milliseconds) a connection allocation should block\n before throwing an exception when the pool is exhausted. Use a negative value\n to block indefinitely."); + data("spring.redis.pool.min-idle", "java.lang.Integer", "0", "Target for the minimum number of idle connections to maintain in the pool. This\n setting only has an effect if it is positive."); + data("spring.redis.port", "java.lang.Integer", "6379", "Redis server port."); + data("spring.redis.sentinel.master", "java.lang.String", null, "Name of Redis server."); + data("spring.redis.sentinel.nodes", "java.lang.String", null, "Comma-separated list of host:port pairs."); + data("spring.resources.add-mappings", "java.lang.Boolean", "true", "Enable default resource handling."); + data("spring.resources.cache-period", "java.lang.Integer", null, "Cache period for the resources served by the resource handler, in seconds."); + data("spring.social.auto-connection-views", "java.lang.Boolean", "false", "Enable the connection status view for supported providers."); + data("spring.social.facebook.app-id", "java.lang.String", null, "Application id."); + data("spring.social.facebook.app-secret", "java.lang.String", null, "Application secret."); + data("spring.social.linkedin.app-id", "java.lang.String", null, "Application id."); + data("spring.social.linkedin.app-secret", "java.lang.String", null, "Application secret."); + data("spring.social.twitter.app-id", "java.lang.String", null, "Application id."); + data("spring.social.twitter.app-secret", "java.lang.String", null, "Application secret."); + data("spring.thymeleaf.cache", "java.lang.Boolean", "true", "Enable template caching."); + data("spring.thymeleaf.check-template-location", "java.lang.Boolean", "true", "Check that the templates location exists."); + data("spring.thymeleaf.content-type", "java.lang.String", "text/html", "Content-Type value."); + data("spring.thymeleaf.enabled", "java.lang.Boolean", "true", "Enable MVC Thymeleaf view resolution."); + data("spring.thymeleaf.encoding", "java.lang.String", "UTF-8", "Template encoding."); + data("spring.thymeleaf.excluded-view-names", "java.lang.String[]", null, "Comma-separated list of view names that should be excluded from resolution."); + data("spring.thymeleaf.mode", "java.lang.String", "HTML5", "Template mode to be applied to templates. See also StandardTemplateModeHandlers."); + data("spring.thymeleaf.prefix", "java.lang.String", "classpath:/templates/", "Prefix that gets prepended to view names when building a URL."); + data("spring.thymeleaf.suffix", "java.lang.String", ".html", "Suffix that gets appended to view names when building a URL."); + data("spring.thymeleaf.view-names", "java.lang.String[]", null, "Comma-separated list of view names that can be resolved."); + data("spring.velocity.allow-request-override", "java.lang.Boolean", null, "Set whether HttpServletRequest attributes are allowed to override (hide) controller\n generated model attributes of the same name."); + data("spring.velocity.cache", "java.lang.Boolean", null, "Enable template caching."); + data("spring.velocity.char-set", "java.lang.String", null, null); + data("spring.velocity.charset", "java.lang.String", null, "Template encoding."); + data("spring.velocity.check-template-location", "java.lang.Boolean", null, "Check that the templates location exists."); + data("spring.velocity.content-type", "java.lang.String", null, "Content-Type value."); + data("spring.velocity.date-tool-attribute", "java.lang.String", null, "Name of the DateTool helper object to expose in the Velocity context of the view."); + data("spring.velocity.enabled", "java.lang.Boolean", null, "Enable MVC view resolution for this technology."); + data("spring.velocity.expose-request-attributes", "java.lang.Boolean", null, "Set whether all request attributes should be added to the model prior to merging\n with the template."); + data("spring.velocity.expose-session-attributes", "java.lang.Boolean", null, "Set whether all HttpSession attributes should be added to the model prior to\n merging with the template."); + data("spring.velocity.expose-spring-macro-helpers", "java.lang.Boolean", null, "Set whether to expose a RequestContext for use by Spring's macro library, under the\n name \"springMacroRequestContext\"."); + data("spring.velocity.number-tool-attribute", "java.lang.String", null, "Name of the NumberTool helper object to expose in the Velocity context of the view."); + data("spring.velocity.prefer-file-system-access", "java.lang.Boolean", "true", "Prefer file system access for template loading. File system access enables hot\n detection of template changes."); + data("spring.velocity.prefix", "java.lang.String", null, "Prefix that gets prepended to view names when building a URL."); + data("spring.velocity.properties", "java.util.Map", null, "Additional velocity properties."); + data("spring.velocity.request-context-attribute", "java.lang.String", null, "Name of the RequestContext attribute for all views."); + data("spring.velocity.resource-loader-path", "java.lang.String", "classpath:/templates/", "Template path."); + data("spring.velocity.suffix", "java.lang.String", null, "Suffix that gets appended to view names when building a URL."); + data("spring.velocity.toolbox-config-location", "java.lang.String", null, "Velocity Toolbox config location, for example \"/WEB-INF/toolbox.xml\". Automatically\n loads a Velocity Tools toolbox definition file and expose all defined tools in the\n specified scopes."); + data("spring.velocity.view-names", "java.lang.String[]", null, "White list of view names that can be resolved."); + data("spring.view.prefix", "java.lang.String", null, "Spring MVC view prefix."); + data("spring.view.suffix", "java.lang.String", null, "Spring MVC view suffix."); + } + + public boolean isEmpty() { + return datas == null || datas.isEmpty(); + } + + public SpringPropertyIndexProvider getIndexProvider() { + return indexProvider; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/.gitignore b/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/.gitignore similarity index 100% rename from vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/.gitignore rename to vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/.gitignore diff --git a/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/.mvn/wrapper/maven-wrapper.jar b/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/.mvn/wrapper/maven-wrapper.jar rename to vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/.mvn/wrapper/maven-wrapper.jar diff --git a/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/.mvn/wrapper/maven-wrapper.properties b/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/.mvn/wrapper/maven-wrapper.properties rename to vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/.mvn/wrapper/maven-wrapper.properties diff --git a/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/mvnw b/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/mvnw similarity index 100% rename from vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/mvnw rename to vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/mvnw diff --git a/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/mvnw.cmd b/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/mvnw.cmd similarity index 100% rename from vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/mvnw.cmd rename to vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/mvnw.cmd diff --git a/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/pom.xml b/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/pom.xml similarity index 100% rename from vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/pom.xml rename to vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/pom.xml diff --git a/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/src/main/java/org/test/TestScopeAnnotationApplication.java b/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/src/main/java/org/test/TestAnnotationsApplication.java similarity index 64% rename from vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/src/main/java/org/test/TestScopeAnnotationApplication.java rename to vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/src/main/java/org/test/TestAnnotationsApplication.java index 90fb713da..3780b4b75 100644 --- a/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/src/main/java/org/test/TestScopeAnnotationApplication.java +++ b/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/src/main/java/org/test/TestAnnotationsApplication.java @@ -4,9 +4,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication -public class TestScopeAnnotationApplication { +public class TestAnnotationsApplication { public static void main(String[] args) { - SpringApplication.run(TestScopeAnnotationApplication.class, args); + SpringApplication.run(TestAnnotationsApplication.class, args); } } diff --git a/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/src/main/java/org/test/TestScopeCompletion.java b/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/src/main/java/org/test/TestScopeCompletion.java similarity index 100% rename from vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-scope-annotation/src/main/java/org/test/TestScopeCompletion.java rename to vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/src/main/java/org/test/TestScopeCompletion.java diff --git a/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/src/main/java/org/test/TestValueCompletion.java b/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/src/main/java/org/test/TestValueCompletion.java new file mode 100644 index 000000000..ba853018a --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/test/resources/test-projects/test-annotations/src/main/java/org/test/TestValueCompletion.java @@ -0,0 +1,17 @@ +package org.test; + +import org.springframework.beans.factory.annotation.Value; + +public class TestValueCompletion { + + @Value("onField") + private String value1; + + @Value("onMethod") + public void method1() { + } + + public void method2(@Value("onParameter") String parameter1) { + } + +}