basic property completion support for value annotations added

This commit is contained in:
Martin Lippert
2017-02-10 11:00:12 +01:00
parent 1356d4a09c
commit ad7203fc7c
55 changed files with 4703 additions and 95 deletions

View File

@@ -22,6 +22,10 @@
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>project-repo</id>
<url>file://${project.basedir}/repo</url>
</repository>
</repositories>
<distributionManagement>
@@ -33,6 +37,11 @@
</distributionManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ide.eclipse</groupId>
<artifactId>org.json</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>commons-maven</artifactId>

View File

@@ -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<String, ConfigurationMetadataSource> sources = new HashMap<String, ConfigurationMetadataSource>();
private final Map<String, ConfigurationMetadataProperty> properties = new HashMap<String, ConfigurationMetadataProperty>();
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<String, ConfigurationMetadataSource> getSources() {
return this.sources;
}
/**
* Return the {@link ConfigurationMetadataProperty properties} defined in this group.
* <p>
* 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<String, ConfigurationMetadataProperty> getProperties() {
return this.properties;
}
}

View File

@@ -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<ValueHint> valueHints = new ArrayList<ValueHint>();
private final List<ValueProvider> valueProviders = new ArrayList<ValueProvider>();
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<ValueHint> getValueHints() {
return this.valueHints;
}
public List<ValueProvider> getValueProviders() {
return this.valueProviders;
}
}

View File

@@ -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;
}
}

View File

@@ -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}.
* <p>
* 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
* <java.lang.String,java.lang.Integer>}.
* <p>
* 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<ValueHint> 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<ValueProvider> 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;
}
}

View File

@@ -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<String, ConfigurationMetadataGroup> getAllGroups();
/**
* Return the properties, indexed by id.
* @return all configuration meta-data properties
*/
Map<String, ConfigurationMetadataProperty> getAllProperties();
}

View File

@@ -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<RawConfigurationMetadata> 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.
* <p>
* 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.
* <p>
* 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<RawConfigurationMetadata> 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<String, ConfigurationMetadataProperty> 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<ValueHint> existing, List<ValueHint> toAdd) {
if (existing.isEmpty()) {
existing.addAll(toAdd);
} else if (toAdd.isEmpty()) {
//nothing to add
} else {
Set<Object> 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);
}
}

View File

@@ -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<String, ConfigurationMetadataProperty> properties = new HashMap<String, ConfigurationMetadataProperty>();
/**
* 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<String, ConfigurationMetadataProperty> getProperties() {
return this.properties;
}
}

View File

@@ -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 + '\'' + '}';
}
}

View File

@@ -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();
}
}

View File

@@ -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<ValueHint> keyHints = new ArrayList<ValueHint>();
private final List<ValueProvider> keyProviders = new ArrayList<ValueProvider>();
private final List<ValueHint> valueHints = new ArrayList<ValueHint>();
private final List<ValueProvider> valueProviders = new ArrayList<ValueProvider>();
/**
* 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<ValueHint> 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<ValueProvider> 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<ValueHint> 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<ValueProvider> getValueProviders() {
return this.valueProviders;
}
}

View File

@@ -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<ConfigurationMetadataSource> groups = parseAllSources(json);
List<ConfigurationMetadataItem> items = parseAllItems(json);
List<ConfigurationMetadataHint> hints = parseAllHints(json);
return new RawConfigurationMetadata(origin, groups, items, hints);
}
private List<ConfigurationMetadataSource> parseAllSources(JSONObject root) {
List<ConfigurationMetadataSource> result = new ArrayList<ConfigurationMetadataSource>();
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<ConfigurationMetadataItem> parseAllItems(JSONObject root) {
List<ConfigurationMetadataItem> result = new ArrayList<ConfigurationMetadataItem>();
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<ConfigurationMetadataHint> parseAllHints(JSONObject root) {
List<ConfigurationMetadataHint> result = new ArrayList<ConfigurationMetadataHint>();
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();
}
}
}

View File

@@ -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.

View File

@@ -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<ConfigurationMetadataSource> sources;
private final List<ConfigurationMetadataItem> items;
private final List<ConfigurationMetadataHint> hints;
RawConfigurationMetadata(Object parsedFrom,
List<ConfigurationMetadataSource> sources,
List<ConfigurationMetadataItem> items,
List<ConfigurationMetadataHint> hints) {
this.origin = parsedFrom;
this.sources = new ArrayList<ConfigurationMetadataSource>(sources);
this.items = new ArrayList<ConfigurationMetadataItem>(items);
this.hints = new ArrayList<ConfigurationMetadataHint>(hints);
for (ConfigurationMetadataItem item : this.items) {
resolveName(item);
}
}
public List<ConfigurationMetadataSource> 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<ConfigurationMetadataItem> getItems() {
return this.items;
}
public List<ConfigurationMetadataHint> 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();
}
}

View File

@@ -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<String, ConfigurationMetadataGroup> allGroups = new HashMap<String, ConfigurationMetadataGroup>();
@Override
public Map<String, ConfigurationMetadataGroup> getAllGroups() {
return Collections.unmodifiableMap(this.allGroups);
}
@Override
public Map<String, ConfigurationMetadataProperty> getAllProperties() {
Map<String, ConfigurationMetadataProperty> properties = new HashMap<String, ConfigurationMetadataProperty>();
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<ConfigurationMetadataSource> 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<String, ConfigurationMetadataProperty> entry : group
.getProperties().entrySet()) {
putIfAbsent(existingGroup.getProperties(), entry.getKey(),
entry.getValue());
}
// Merge sources
for (Map.Entry<String, ConfigurationMetadataSource> 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 <V> void putIfAbsent(Map<String, V> map, String key, V value) {
if (!map.containsKey(key)) {
map.put(key, value);
}
}
}

View File

@@ -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
+ '\'' + '}';
}
}

View File

@@ -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.
* <p>
* 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<String, Object> parameters = new LinkedHashMap<String, Object>();
/**
* 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<String, Object> getParameters() {
return this.parameters;
}
@Override
public String toString() {
return "ValueProvider{" + "name='" + this.name + ", parameters=" + this.parameters
+ '}';
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
});
}

View File

@@ -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 {

View File

@@ -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<PropertyInfo> index;
public ValueCompletionProcessor(FuzzyMap<PropertyInfo> index) {
this.index = index;
}
public void collectCompletionsForValueAnnotation(ASTNode node, Annotation annotation, ITypeBinding type,
List<ICompletionProposal> completions, int offset, IDocument doc) {
try {
// case: @Value(<*>)
if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) {
List<Match<PropertyInfo>> matches = findMatches("");
for (Match<PropertyInfo> 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<Match<PropertyInfo>> matches = findMatches(prefix);
for (Match<PropertyInfo> 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<Match<PropertyInfo>> matches = findMatches(prefix);
for (Match<PropertyInfo> 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<Match<PropertyInfo>> findMatches(String prefix) {
List<Match<PropertyInfo>> matches = index.find(camelCaseToHyphens(prefix));
return matches;
}
}

View File

@@ -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;
}
}

View File

@@ -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.
* <p>
* 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.
* <p>
* This implementation therefore does the following:
* <ul>
* <li>Limit the duration of time spent on the UI thread.
* <li>Cache results of searches for a limited time.
* <li>Speedup queries for successive queries by using the already cached result of a similar (prefix) query.
* <li>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.
* </ul>
*
* 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<Tuple2<String,String>, CacheEntry> cache = createCache();
private class CacheEntry {
boolean isComplete = false;
int count = 0;
Flux<StsValueHint> values;
public CacheEntry(String query, Flux<StsValueHint> 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<StsValueHint> getValues(IJavaProject javaProject, String query) {
Tuple2<String, String> 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.
* <p>
* Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache.
*/
private Flux<StsValueHint> 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<StsValueHint> getValuesAsync(IJavaProject javaProject, String query);
private Tuple2<String,String> key(IJavaProject javaProject, String query) {
return Tuples.of(javaProject==null?null:javaProject.getElementName(), query);
}
protected <K,V> Cache<K,V> createCache() {
return CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).expireAfterAccess(1, TimeUnit.MINUTES).build();
}
public static void restoreDefaults() {
TIMEOUT = DEFAULT_TIMEOUT;
}
}

View File

@@ -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.
* <p>
* 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<Map<String, Object>, 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 <K,V> Function<K,V> applyOn(long duration, TimeUnit unit, Function<K,V> func) {
Cache<K,V> 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<String, Object> 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<String, Object> 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<StsValueHint> 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<IType> 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()));
}
}
}

View File

@@ -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<PropertyInfo> 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<PropertyInfo> 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;
}
}

View File

@@ -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<PropertyInfo> index;
private IndexNavigator(FuzzyMap<PropertyInfo> index) {
this.index = index;
}
private IndexNavigator(FuzzyMap<PropertyInfo> index, String prefix) {
this.index = index;
this.prefix = prefix;
}
public static IndexNavigator with(FuzzyMap<PropertyInfo> 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<Match<PropertyInfo>> findMatching(String query) {
if (!StringUtil.hasText(prefix)) {
return index.find(query);
} else {
String dottedPrefix = prefix +".";
List<Match<PropertyInfo>> candidates = index.find(dottedPrefix + query);
if (!candidates.isEmpty()) {
//TODO: we can do better than this using treemap to narrow based on
// prefix
List<Match<PropertyInfo>> matches = new ArrayList<Match<PropertyInfo>>(candidates.size());
for (Match<PropertyInfo> 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;
}
}

View File

@@ -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.
* <p>
* 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<Map<String, Object>, ValueProviderStrategy> FACTORY = (params) -> INSTANCE;
@Override
protected Flux<StsValueHint> 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());
}
}

View File

@@ -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.
* <p>
* 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<String, Object>());
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;
}
}

View File

@@ -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);
}
}

View File

@@ -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<PropertySource> sources;
private Deprecation deprecation;
private ImmutableList<ValueHint> valueHints;
private ImmutableList<ValueHint> keyHints;
private ValueProviderStrategy valueProvider;
private ValueProviderStrategy keyProvider;
public PropertyInfo(String id, String type, String name,
Object defaultValue, String description,
Deprecation deprecation,
List<ValueHint> valueHints,
List<ValueHint> keyHints,
ValueProviderStrategy valueProvider,
ValueProviderStrategy keyProvider,
List<PropertySource> 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<PropertySource> 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<PropertySource>();
}
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<ValueHint> hints) {
Builder<ValueHint> builder = ImmutableList.builder();
builder.addAll(valueHints);
builder.addAll(hints);
valueHints = builder.build();
}
public void addKeyHints(List<ValueHint> hints) {
Builder<ValueHint> builder = ImmutableList.builder();
builder.addAll(keyHints);
builder.addAll(hints);
keyHints = builder.build();
}
}

View File

@@ -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<StsValueHint> 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<StsValueHint> 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<StsValueHint> getValuesAsync(IJavaProject javaProject, String query) {
return Flux.fromStream(javaProject.getClasspath().getClasspathResources().distinct().map(StsValueHint::create));
}
}
}

View File

@@ -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<Listener<SpringPropertiesIndexManager>> {
private Map<IJavaProject, SpringPropertyIndex> indexes = null;
private final ValueProviderRegistry valueProviders;
private static int progressIdCt = 0;
public SpringPropertiesIndexManager(ValueProviderRegistry valueProviders) {
this.valueProviders = valueProviders;
}
public synchronized FuzzyMap<PropertyInfo> 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<SpringPropertiesIndexManager> l : getListeners()) {
l.changed(this);
}
}
}
private static synchronized String getProgressId() {
return DefaultSpringPropertyIndexProvider.class.getName()+ (progressIdCt++);
}
}

View File

@@ -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<PropertyInfo> {
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<ConfigurationMetadataProperty> 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<Match<PropertyInfo>> allData = this.find("");
for (Match<PropertyInfo> 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<PropertyInfo> 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;
}
}

View File

@@ -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<PropertyInfo> getIndex(IDocument doc);
}

View File

@@ -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<String, Function<Map<String, Object>, ValueProviderStrategy>> registry = new HashMap<>();
public interface ValueProviderStrategy {
Flux<StsValueHint> getValues(IJavaProject javaProject, String query);
default Collection<StsValueHint> 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<Map<String, Object>, ValueProviderStrategy> algo) {
registry.put(id, algo);
}
/**
* Resolve a list of {@link ValueProvider}s to a {@link ValueProviderStrategy}.
* <p>
* 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<ValueProvider> providerDescriptors) {
if (CollectionUtil.hasElements(providerDescriptors)) {
for (ValueProvider descriptor : providerDescriptors) {
Function<Map<String, Object>, ValueProviderStrategy> factory = registry.get(descriptor.getName());
if (factory!=null) {
Map<String, Object> params = descriptor.getParameters();
return factory.apply(params);
}
}
}
return null;
}
}

View File

@@ -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.
* <p>
* 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.
* <p>
* 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();
}
};
}
}

View File

@@ -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<Renderable> builder = ImmutableList.builder();
builder.add(bold(""+hint.getValue()));
builder.add(paragraph(hint.getDescription()));
return concat(builder.build());
}
}

View File

@@ -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<String> 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> 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<Deprecation> 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();
}
}

View File

@@ -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.
* <p>
* The collection can then be searched for items who's key matches
* simple 'fuzzy' patterns.
*/
public abstract class FuzzyMap<E> implements Iterable<E> {
private static final Logger LOG = Logger.getLogger(FuzzyMap.class.getName());
public static class Match<E> {
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 <E> Match<E> getBest(Collection<Match<E>> matches) {
double bestScore = Double.NEGATIVE_INFINITY;
Match<E> best = null;
for (Match<E> 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<E> iterator() {
return entries.values().iterator();
}
private TreeMap<String,E> entries = new TreeMap<String, E>();
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.
* <p>
* 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<Match<E>> 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<Match<E>> matches = new ArrayList<Match<E>>(entries.size());
for (E v : entries.values()) {
matches.add(new Match<E>(pattern, 1.0, v));
}
return matches;
} else {
//TODO: optimize somehow with a smarter index? (right now searches all map entries sequentially)
ArrayList<Match<E>> matches = new ArrayList<Match<E>>();
for (Entry<String, E> e : entries.entrySet()) {
String key = e.getKey();
double score = FuzzyMatcher.matchScore(pattern, key);
if (score!=0.0) {
matches.add(new Match<E>(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<String, E> ceiln = entries.ceilingEntry(propertyName);
Entry<String, E> floor = entries.floorEntry(propertyName);
Entry<String, E> 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();
}
}

View File

@@ -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<T> {
void changed(T info);
}

View File

@@ -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<T> {
private ListenerList<T> 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<T> getListeners() {
return (Iterable<T>) Arrays.asList(listeners.getListeners());
}
}

View File

@@ -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<BootJavaLanguageServer>() {
@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);

View File

@@ -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<BootJavaLanguageServer>() {
@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<CompletionItem> 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());
}
}

View File

@@ -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>() {
//
// 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<Path>() {
//
// @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);

View File

@@ -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<String, ConfigurationMetadataProperty> 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<PropertyInfo> 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<ValueHint> 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<ValueHint> 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<java.lang.String>", 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<java.lang.String>", 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<java.lang.String,java.lang.String>", 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<java.lang.String,java.lang.Object>", 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<java.lang.String>", 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<java.lang.String>", 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<java.lang.String,java.lang.String>", 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<java.lang.String,java.lang.String>", 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<java.lang.String,java.lang.String>", 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<? extends groovy.text.markup.BaseTemplate>", 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<java.lang.String,java.lang.Object>", 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<com.fasterxml.jackson.databind.DeserializationFeature,java.lang.Boolean>", null, "Jackson on/off features that affect the way Java objects are deserialized.");
data("spring.jackson.generator", "java.util.Map<com.fasterxml.jackson.core.JsonGenerator.Feature,java.lang.Boolean>", null, "Jackson on/off features for generators.");
data("spring.jackson.mapper", "java.util.Map<com.fasterxml.jackson.databind.MapperFeature,java.lang.Boolean>", null, "Jackson general purpose on/off features.");
data("spring.jackson.parser", "java.util.Map<com.fasterxml.jackson.core.JsonParser.Feature,java.lang.Boolean>", 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<com.fasterxml.jackson.databind.SerializationFeature,java.lang.Boolean>", 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<java.lang.String,java.lang.String>", 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<java.lang.String,java.lang.String>", 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<java.lang.String,java.lang.String>", 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<java.lang.Object>", 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<java.lang.String,java.lang.String>", 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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) {
}
}