Merge application-properties-metadata, project-test-harness

properties-editor-test-harness into vscode-boot-properties
This commit is contained in:
BoykoAlex
2016-12-12 13:19:51 -05:00
parent 75e550713f
commit 4309f3535e
212 changed files with 417 additions and 1249 deletions

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

@@ -13,12 +13,12 @@ package org.springframework.ide.vscode.boot;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory;
import org.springframework.ide.vscode.boot.common.RelaxedNameConfig;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.boot.properties.completions.SpringPropertiesCompletionEngine;
import org.springframework.ide.vscode.boot.properties.hover.PropertiesHoverInfoProvider;
import org.springframework.ide.vscode.boot.properties.reconcile.SpringPropertiesReconcileEngine;

View File

@@ -12,9 +12,9 @@ package org.springframework.ide.vscode.boot;
import java.io.IOException;
import org.springframework.ide.vscode.application.properties.metadata.DefaultSpringPropertyIndexProvider;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider;
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;

View File

@@ -7,15 +7,15 @@ import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.application.properties.metadata.hints.HintProvider;
import org.springframework.ide.vscode.application.properties.metadata.hints.HintProviders;
import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.hints.HintProvider;
import org.springframework.ide.vscode.boot.metadata.hints.HintProviders;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeParser;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.boot.properties.reconcile.PropertyNavigator;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.languageserver.util.TextDocument;

View File

@@ -12,7 +12,7 @@ import static org.springframework.ide.vscode.commons.util.Renderables.text;
import java.util.Collection;
import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.StringUtil;

View File

@@ -11,12 +11,12 @@
package org.springframework.ide.vscode.boot.common;
import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap.Match;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeParser;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
import org.springframework.ide.vscode.boot.metadata.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.languageserver.completion.ScoreableProposal;

View File

@@ -1,7 +1,7 @@
package org.springframework.ide.vscode.boot.common;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode;
/**
* Config object that determines some aspects of how the freedom of 'relaxed name binding'

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,143 @@
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.findType("java.lang.Object") : javaProject.findType(target);
if (targetType == null) {
return Flux.empty();
}
Set<IType> allSubclasses = javaProject
.allSubtypesOf(targetType)
.filter(t -> Flags.isPublic(t.getFlags()) && !concrete || !isAbstract(t))
.collect(Collectors.toSet())
.block();
if (allSubclasses.isEmpty()) {
return Flux.empty();
} else {
return javaProject
.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,44 @@
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.IDocument;
public class DefaultSpringPropertyIndexProvider implements SpringPropertyIndexProvider {
private JavaProjectFinder javaProjectFinder;
private SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager(ValueProviderRegistry.getDefault());
private ProgressService progressService = (id, msg) -> { /*ignore*/ };
private static int progressIdCt = 0;
public DefaultSpringPropertyIndexProvider(JavaProjectFinder javaProjectFinder) {
this.javaProjectFinder = javaProjectFinder;
}
@Override
public FuzzyMap<PropertyInfo> getIndex(IDocument doc) {
String progressId = getProgressId();
progressService.progressEvent(progressId, "Indexing Spring Boot Properties...");
try {
IJavaProject jp = javaProjectFinder.find(doc);
if (jp!=null) {
return indexManager.get(jp);
}
} finally {
progressService.progressEvent(progressId, null);
}
return null;
}
private static synchronized String getProgressId() {
return DefaultSpringPropertyIndexProvider.class.getName()+ (progressIdCt++);
}
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,41 @@
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
.fuzzySearchPackages(query)
.map(t -> Tuples.of(StsValueHint.create(t.getT1()), t.getT2())),
javaProject
.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,136 @@
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,222 @@
/*******************************************************************************
* 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 org.springframework.ide.vscode.boot.metadata.hints.HintProvider;
import org.springframework.ide.vscode.boot.metadata.hints.HintProviders;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeParser;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
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 HintProvider getHints(TypeUtil typeUtil, boolean dimensionAware) {
Type type = TypeParser.parse(this.type);
if (TypeUtil.isMap(type)) {
return HintProviders.forMap(keyHints(typeUtil), valueHints(typeUtil), TypeUtil.getDomainType(type), dimensionAware);
} else if (TypeUtil.isSequencable(type)) {
if (dimensionAware) {
if (TypeUtil.isSequencable(type)) {
return HintProviders.forDomainAt(valueHints(typeUtil), TypeUtil.getDimensionality(type));
} else {
return HintProviders.forHere(valueHints(typeUtil));
}
} else {
return HintProviders.forAllValueContexts(valueHints(typeUtil));
}
} else {
return HintProviders.forHere(valueHints(typeUtil));
}
}
private HintProvider keyHints(TypeUtil typeUtil) {
return HintProviders.basic(typeUtil.getJavaProject(), keyHints, keyProvider);
}
private HintProvider valueHints(TypeUtil typeUtil) {
return HintProviders.basic(typeUtil.getJavaProject(), valueHints, valueProvider);
}
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,60 @@
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,59 @@
/*******************************************************************************
* 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;
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;
/**
* 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;
final private ValueProviderRegistry valueProviders;
public SpringPropertiesIndexManager(ValueProviderRegistry valueProviders) {
this.valueProviders = valueProviders;
}
public synchronized FuzzyMap<PropertyInfo> get(IJavaProject project) {
if (indexes==null) {
indexes = new HashMap<>();
}
SpringPropertyIndex index = indexes.get(project);
if (index==null) {
index = new SpringPropertyIndex(valueProviders, project.getClasspath());
indexes.put(project, index);
}
return index;
}
public synchronized void clear() {
if (indexes!=null) {
indexes.clear();
for (Listener<SpringPropertiesIndexManager> l : getListeners()) {
l.changed(this);
}
}
}
}

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.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,85 @@
/*******************************************************************************
* 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.hints;
import java.util.Collection;
import java.util.List;
import org.springframework.boot.configurationmetadata.ValueHint;
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
/**
* A single hint provider that combines the two kinds of 'hint' metadata that spring boot
* may contain (namely 'values' data and 'valueProvider' data).
* <p>
* This basic hint provider is not context-aware and returns the same value hints regardless of the
* yaml context.
* <p>
* This hint provider doesn't provide 'property' hints because property hints typically require
* context information (i.e the type of the parent context).
* <p>
* To make this provider 'context aware' it can be wrapped in an adapter created by calling
* one of the static methods in the {@link HintProviders} class.
*
* @author Kris De Volder
*/
public class BasicHintProvider implements HintProvider {
private IJavaProject javaProject;
private ImmutableList<ValueHint> valueHints;
private ValueProviderStrategy valueProvider;
public BasicHintProvider(IJavaProject javaProject,
ImmutableList<ValueHint> valueHints,
ValueProviderStrategy valueProvider) {
this.javaProject = javaProject;
this.valueHints = valueHints;
this.valueProvider = valueProvider;
}
@Override
public HintProvider traverse(YamlPathSegment s) throws Exception {
//since this provider is not context sensitive it just returns itself (So hints provides in 'sub-contexts'
// are exaclty the same as hints in the parent context.
return this;
}
@Override
public List<StsValueHint> getValueHints(String query) {
Builder<StsValueHint> builder = ImmutableList.builder();
if (CollectionUtil.hasElements(valueHints)) {
for (ValueHint hint : valueHints) {
builder.add(StsValueHint.create(hint));
}
}
if (valueProvider!=null) {
Collection<StsValueHint> provided = valueProvider.getValuesNow(javaProject, query);
if (CollectionUtil.hasElements(provided)) {
builder.addAll(provided);
}
}
return builder.build();
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
return ImmutableList.of();
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* 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.hints;
import java.util.List;
import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
import org.springframework.ide.vscode.commons.yaml.path.YamlNavigable;
/**
* @author Kris De Volder
*/
public interface HintProvider extends YamlNavigable<HintProvider> {
List<StsValueHint> getValueHints(String query);
List<TypedProperty> getPropertyHints(String query);
}

View File

@@ -0,0 +1,222 @@
/*******************************************************************************
* 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.hints;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.configurationmetadata.ValueHint;
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
import com.google.common.collect.ImmutableList;
/**
* Methods for creating hints providers that provide hint in specific kind of context.
*
* @author Kris De Volder
*/
public class HintProviders {
/**
* HintProvider that never returns any hints. This should be used
* instead of null pointer.
*/
public static final HintProvider NULL = new HintProvider() {
@Override
public HintProvider traverse(YamlPathSegment s) throws Exception {
return NULL;
}
@Override
public List<StsValueHint> getValueHints(String query) {
return ImmutableList.of();
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
return ImmutableList.of();
}
};
/**
* Creates a non-context-aware hint provider. Typically a hint provider is created by composing the result of
* this with one or more of the other methods in this class to wrap the basic provider so it becomes context-aware.
*/
public static HintProvider basic(IJavaProject jp, final ImmutableList<ValueHint> valueHints, final ValueProviderStrategy valueProvider) {
if (!CollectionUtil.hasElements(valueHints) && valueProvider==null) {
return NULL;
}
return new BasicHintProvider(jp, valueHints, valueProvider);
}
/**
* Create a hint provider that will return the given hints in the context following
* a traversal that goes down into a 'domain of' context a given number of times.
*/
public static HintProvider forDomainAt(final HintProvider valueHints, final int dim) {
if (isNull(valueHints)) {
return NULL;
}
if (dim==0) {
return forHere(valueHints);
}
return new HintProvider() {
public HintProvider traverse(YamlPathSegment s) throws Exception {
switch (s.getType()) {
case VAL_AT_INDEX:
case VAL_AT_KEY:
return forDomainAt(valueHints, dim-1);
default:
return NULL;
}
}
public List<StsValueHint> getValueHints(String query) {
return ImmutableList.of();
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
return ImmutableList.of();
}
};
}
/**
* Only returns the given hints in this context but not one of its 'sub contexts'.
*/
public static HintProvider forHere(final HintProvider valueHints) {
if (isNull(valueHints)) {
return NULL;
}
return new HintProvider() {
@Override
public HintProvider traverse(YamlPathSegment s) throws Exception {
return NULL;
}
@Override
public List<StsValueHint> getValueHints(String query) {
return valueHints.getValueHints(query);
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
return ImmutableList.of();
}
};
}
/**
* REturns the given hints in this context and any of its subcontexts that expect values.
*/
public static HintProvider forAllValueContexts(final HintProvider valueProvider) {
if (isNull(valueProvider)) {
return NULL;
}
return new HintProvider() {
@Override
public HintProvider traverse(YamlPathSegment s) throws Exception {
switch (s.getType()) {
case VAL_AT_INDEX:
case VAL_AT_KEY:
return this;
default:
return NULL;
}
}
@Override
public List<StsValueHint> getValueHints(String query) {
return valueProvider.getValueHints(query);
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
return ImmutableList.of();
}
};
}
public static boolean isNull(HintProvider p) {
//If everyone is nice and doesn't ever use null pointers then the p==null check is
// not needed. But just in case.
return p == NULL || p==null;
}
public static HintProvider forMap(HintProvider _keyProvider, HintProvider _valueProvider, final Type valueType, final boolean dimensionAware) {
final HintProvider keyProvider = notNull(_keyProvider);
final HintProvider valueProvider = notNull(_valueProvider);
if (isNull(keyProvider) && isNull(valueProvider)) {
return NULL;
}
return new HintProvider() {
@Override
public HintProvider traverse(YamlPathSegment s) throws Exception {
switch (s.getType()) {
case VAL_AT_INDEX:
case VAL_AT_KEY:
if (dimensionAware) {
return forHere(valueProvider);
} else {
return forAllValueContexts(valueProvider);
}
default:
return NULL;
}
}
@Override
public List<StsValueHint> getValueHints(String query) {
if (dimensionAware) {
//pickier, completions only suggested in the domain of map, but not for map itself.
return ImmutableList.of();
} else {
return valueProvider.getValueHints(query);
}
}
@Override
public List<TypedProperty> getPropertyHints(String query) {
List<StsValueHint> keyHints = keyProvider.getValueHints(query);
if (CollectionUtil.hasElements(keyHints)) {
List<TypedProperty> props = new ArrayList<>(keyHints.size());
for (StsValueHint keyHint : keyHints) {
Object key = keyHint.getValue();
if (key instanceof String) {
props.add(new TypedProperty((String)key, valueType, null));
}
}
return props;
}
return ImmutableList.of();
}
};
}
/**
* Protection against bad code passing us null pointers.
*/
private static HintProvider notNull(HintProvider p) {
if (p==null) {
return NULL;
}
return p;
}
}

View File

@@ -0,0 +1,144 @@
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.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.util.DeprecationUtil;
import org.springframework.ide.vscode.commons.java.IJavaElement;
import org.springframework.ide.vscode.commons.java.IJavaProject;
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.Log;
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 className(String fqName, TypeUtil typeUtil) {
try {
IJavaProject jp = typeUtil.getJavaProject();
if (jp!=null) {
IType type = jp.findType(fqName);
if (type!=null) {
return create(type);
}
}
} catch (Exception e) {
Log.log(e);
}
return 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,22 @@
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,236 @@
/*******************************************************************************
* 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.types;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.ide.vscode.commons.java.IArrayType;
import org.springframework.ide.vscode.commons.java.IClassType;
import org.springframework.ide.vscode.commons.java.IJavaType;
import org.springframework.ide.vscode.commons.java.IParameterizedType;
import org.springframework.ide.vscode.commons.java.IPrimitiveType;
import org.springframework.ide.vscode.commons.java.IVoidType;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
/**
* @author Kris De Volder
*/
public class Type implements YType {
private final String erasure;
private final Type[] params;
public Type(String erasure, Type[] params) {
this.erasure = erasure;
this.params = params;
}
public boolean isGeneric() {
return params!=null;
}
public String getErasure() {
return erasure;
}
public Type[] getParams() {
return params;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
toString(buf);
return buf.toString();
}
private void toString(StringBuilder buf) {
buf.append(getErasure());
if (isGeneric()) {
buf.append("<");
boolean first = true;
for (Type param : getParams()) {
if (!first) {
buf.append(",");
}
param.toString(buf);
first = false;
}
buf.append(">");
}
}
/**
* Attempt to convert given typeSignature to a corresponding Type object.
* <p>
* Not all valid typeSig have a representation as a Type object. This may
* return null if no corresponding representation can be constructed.
*/
// public static Type fromSignature(String typeSig, IType context) {
// //TODO: does this work correctly with nested types (i.e like Map$Entry)
// Type type = TYPE_FROM_SIG.get(typeSig);
// if (type!=null) {
// return type;
// }
// int kind = Signature.getTypeSignatureKind(typeSig);
// //Essentially, Type object only able to represent class types with with generic parameters
// // as long as these generic parameters are fully concrete (i.e. do not contain unbound type
// // variables. For now only support the simplest case (no generics) and bail out returning null if we
// // see something we don't understand.
// if (kind==Signature.CLASS_TYPE_SIGNATURE) {
// boolean shouldResolve = typeSig.charAt(0)==Signature.C_UNRESOLVED;
// String erasure = Signature.getTypeErasure(typeSig);
// String pkg = Signature.getSignatureQualifier(erasure);
// String nam = Signature.getSignatureSimpleName(erasure);
// String[] params = Signature.getTypeParameters(typeSig);
// String[] args = Signature.getTypeArguments(typeSig);
// if (shouldResolve) {
// erasure = tryToResolve(qualifiedName(pkg, nam), context);
// } else {
// erasure = qualifiedName(pkg, nam);
// }
// if (ArrayUtils.hasElements(params)) {
// //TODO: handle this case
// return null;
// } else if (ArrayUtils.hasElements(args)) {
// Type[] argTypes = new Type[args.length];
// for (int i = 0; i < argTypes.length; i++) {
// argTypes[i] = fromSignature(args[i], context);
// }
// return new Type(erasure, argTypes);
// } else {
// return new Type(erasure, null);
// }
// } else if (kind==Signature.ARRAY_TYPE_SIGNATURE) {
// Type elementType = fromSignature(Signature.getElementType(typeSig), context);
// if (elementType!=null) {
// int arrayCount = Signature.getArrayCount(typeSig);
// return elementType.asArray(arrayCount);
// }
// }
// return null;
// }
public static Type fromJavaType(IJavaType javaType) {
if (javaType instanceof IPrimitiveType || javaType instanceof IVoidType) {
Type type = TYPE_FROM_SIG.get(javaType.name());
if (type != null) {
return type;
}
} else if (javaType instanceof IClassType) {
return new Type(javaType.name(), null);
} else if (javaType instanceof IParameterizedType) {
IParameterizedType parameterizedType = (IParameterizedType) javaType;
List<Type> arguments = parameterizedType.arguments().map(Type::fromJavaType).collect(Collectors.toList());
return new Type(parameterizedType.name(), arguments.toArray(new Type[arguments.size()]));
} else if (javaType instanceof IArrayType) {
IArrayType arrayType = (IArrayType) javaType;
Type elementType = fromJavaType(arrayType.component());
if (elementType!=null) {
return elementType.asArray(arrayType.dimensions());
}
}
return null;
}
public Type asArray(int arrayCount) {
Assert.isLegal(arrayCount>0);
StringBuilder arrayErasure = new StringBuilder(erasure);
for (int i = 0; i < arrayCount; i++) {
arrayErasure.append("[]");
}
return new Type(arrayErasure.toString(), params);
}
// private static String qualifiedName(String pkg, String nam) {
// if (StringUtil.hasText(pkg)) {
// return pkg + "." + nam;
// } else {
// return nam;
// }
// }
//
// private static String tryToResolve(String typeName, IType context) {
// try {
// String[][] resolved = context.resolveType(typeName);
// if (ArrayUtils.hasElements(resolved)) {
// String pkg = resolved[0][0];
// String nam = resolved[0][1];
// if (StringUtil.hasText(pkg)) {
// return pkg+"."+nam;
// } else {
// //No . in front of default package
// return nam;
// }
// }
// } catch (Exception e) {
// Log.log(e);
// }
// return typeName;
// }
//////////////////////////////////////////////////
/**
* Map of some known / common type signatures and their corresponding 'Type' representation.
* Note that springboot metadata 'normalizes' all primitive types to their corresponding box
* types. So we do the same here.
*/
private static final Map<String,Type> TYPE_FROM_SIG = new HashMap<String, Type>();
static {
sig2type("B", Byte.class);
sig2type("C", Character.class);
sig2type("D", Double.class);
sig2type("F", Float.class);
sig2type("I", Integer.class);
sig2type("J", Long.class);
sig2type("S", Short.class);
sig2type("V", Void.class);
sig2type("Z", Boolean.class);
}
private static void sig2type(String sig, Class<?> cls) {
TYPE_FROM_SIG.put(sig, TypeParser.parse(cls.getName()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((erasure == null) ? 0 : erasure.hashCode());
result = prime * result + Arrays.hashCode(params);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Type other = (Type) obj;
if (erasure == null) {
if (other.erasure != null)
return false;
} else if (!erasure.equals(other.erasure))
return false;
if (!Arrays.equals(params, other.params))
return false;
return true;
}
}

View File

@@ -0,0 +1,134 @@
package org.springframework.ide.vscode.boot.metadata.types;
import java.util.ArrayList;
import java.util.StringTokenizer;
import org.springframework.ide.vscode.commons.util.StringUtil;
/**
* Converts types in notation used by spring properties metadata into a 'Structured' form
*
* @author Kris De Volder
*/
public class TypeParser {
private static final String DELIM = "<>,";
/**
* Wrapper around StringTokenizer that manages a single lookahead token.
* So it can implement 'peekToken()' method.
*/
private static class Tokener {
private String lookahead;
private StringTokenizer tokens;
public Tokener(String input) {
this.tokens = new StringTokenizer(input, DELIM, true);
}
/**
* Fetch next token. Returns null if there are no more tokens.
*/
public String nextToken() {
if (lookahead!=null) {
try {
return lookahead;
} finally {
lookahead = null;
}
} else if (tokens.hasMoreTokens()) {
return tokens.nextToken();
}
return null;
}
/**
* Fetch the next token without consuming it.
* Returns null if there are no more tokens.
*/
public String peekToken() {
if (lookahead!=null) {
return lookahead;
} else if (tokens.hasMoreTokens()) {
lookahead = tokens.nextToken();
return lookahead;
}
return null;
}
}
private Tokener input;
private TypeParser(String input) {
this.input = new Tokener(input);
}
public static Type parse(String str) {
if (StringUtil.hasText(str)) {
return new TypeParser(str).parseType();
}
return null;
}
private Type parseType() {
String ident = input.nextToken();
String token = input.peekToken();
if ("<".equals(token)) {
ArrayList<Type> params = parseParams();
return new Type(ident, params.toArray(new Type[params.size()]));
} else {
return new Type(ident, null);
}
}
private ArrayList<Type> parseParams() {
skip("<");
try {
return parseParamList(new ArrayList<Type>());
} finally {
skip(">");
}
}
private ArrayList<Type> parseParamList(ArrayList<Type> params) {
//parse params separate by ",'
String tok = input.peekToken();
if (isIdent(tok)) {
params.add(parseType());
if (skip(",")) {
return parseParamList(params);
}
}
return params;
}
/**
* Skip an expected token, or do nothing if the next token is
* something unexpected.
* @return whether token was skipped.
*/
private boolean skip(String expected) {
String t = input.peekToken();
if (expected.equals(t)) {
input.nextToken();
return true;
}
return false;
}
public boolean isIdent(String token) {
return token!=null && !isSeparator(token);
}
private boolean isSeparator(String token) {
if (token!=null && token.length()==1) {
int len = DELIM.length();
char c = token.charAt(0);
for (int i = 0; i < len; i++) {
if (DELIM.charAt(i)==c) {
return true;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,955 @@
package org.springframework.ide.vscode.boot.metadata.types;
import static org.springframework.ide.vscode.commons.util.ArrayUtils.firstElement;
import static org.springframework.ide.vscode.commons.util.ArrayUtils.lastElement;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Provider;
import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.ide.vscode.boot.metadata.ResourceHintProvider;
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.boot.metadata.util.DeprecationUtil;
import org.springframework.ide.vscode.commons.java.Flags;
import org.springframework.ide.vscode.commons.java.IField;
import org.springframework.ide.vscode.commons.java.IJavaElement;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IMethod;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.util.AlwaysFailingParser;
import org.springframework.ide.vscode.commons.util.ArrayUtils;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.util.EnumValueParser;
import org.springframework.ide.vscode.commons.util.HtmlSnippet;
import org.springframework.ide.vscode.commons.util.LazyProvider;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.ValueParser;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.MediaType;
import reactor.core.publisher.Flux;
/**
* Utilities to work with types represented as Strings as returned by
* Spring config metadata apis.
*
* @author Kris De Volder
*/
public class TypeUtil {
private static abstract class RadixableParser implements ValueParser {
protected abstract Object parse(String str, int radix);
@Override
public Object parse(String str) {
if (str.startsWith("0")) {
if (str.startsWith("0x")||str.startsWith("0X")) {
return parse(str.substring(2), 16);
} else if (str.startsWith("0b") || str.startsWith("0B")) {
return parse(str.substring(2), 2);
} else {
return parse(str, 8);
}
}
return parse(str, 10);
}
}
private static final Object OBJECT_TYPE_NAME = Object.class.getName();
private static final String STRING_TYPE_NAME = String.class.getName();
private static final String INET_ADDRESS_TYPE_NAME = InetAddress.class.getName();
private static final String CLASS_TYPE_NAME = Class.class.getName();
public enum BeanPropertyNameMode {
HYPHENATED(true,false), //bean property name in hyphenated form. E.g 'some-property-name'
CAMEL_CASE(false,true), //bean property name in camelCase. E.g. 'somePropertyName'
ALIASED(true,true); //use both as aliases of one another.
private final boolean includesHyphenated;
private final boolean includesCamelCase;
BeanPropertyNameMode(boolean hyphenated, boolean camelCase) {
this.includesCamelCase = camelCase;
this.includesHyphenated = hyphenated;
}
public boolean includesHyphenated() {
return includesHyphenated;
}
public boolean includesCamelCase() {
return includesCamelCase;
}
}
public enum EnumCaseMode {
LOWER_CASE, //convert enum names to lower case
ORIGNAL, //keep orignal enum name
ALIASED //use both lower-cased and original names as aliases of one another
}
private IJavaProject javaProject;
public TypeUtil(IJavaProject jp) {
//Note javaProject is allowed to be null, but only in unit testing context
// (This is so some tests can be run without an explicit jp needing to be created)
this.javaProject = jp;
}
private static final Map<String, String> PRIMITIVE_TYPE_NAMES = new HashMap<>();
private static final Map<String, Type> PRIMITIVE_TO_BOX_TYPE = new HashMap<>();
static {
PRIMITIVE_TYPE_NAMES.put("java.lang.Boolean", "boolean");
PRIMITIVE_TYPE_NAMES.put("java.lang.Byte", "byte");
PRIMITIVE_TYPE_NAMES.put("java.lang.Short", "short");
PRIMITIVE_TYPE_NAMES.put("java.lang.Integer","int");
PRIMITIVE_TYPE_NAMES.put("java.lang.Long", "long");
PRIMITIVE_TYPE_NAMES.put("java.lang.Double", "double");
PRIMITIVE_TYPE_NAMES.put("java.lang.Float", "float");
PRIMITIVE_TYPE_NAMES.put("java.lang.Character", "char");
for (Entry<String, String> e : PRIMITIVE_TYPE_NAMES.entrySet()) {
PRIMITIVE_TO_BOX_TYPE.put(e.getValue(), new Type(e.getKey(), null));
}
}
public static final Type INTEGER_TYPE = new Type("java.lang.Integer", null);
private static final Set<String> ASSIGNABLE_TYPES = new HashSet<>(Arrays.asList(
"java.lang.Boolean",
"java.lang.String",
"java.lang.Short",
"java.lang.Integer",
"java.lang.Long",
"java.lang.Double",
"java.lang.Float",
"java.lang.Character",
"java.lang.Byte",
INET_ADDRESS_TYPE_NAME,
CLASS_TYPE_NAME,
"java.lang.String[]"
));
private static final Set<String> ATOMIC_TYPES = new HashSet<>(PRIMITIVE_TYPE_NAMES.keySet());
static {
ATOMIC_TYPES.add(INET_ADDRESS_TYPE_NAME);
ATOMIC_TYPES.add(STRING_TYPE_NAME);
ATOMIC_TYPES.add(CLASS_TYPE_NAME);
}
private static final Map<String, String[]> TYPE_VALUES = new HashMap<>();
static {
TYPE_VALUES.put("java.lang.Boolean", new String[] { "true", "false" });
}
private static final Map<String,ValueParser> VALUE_PARSERS = new HashMap<>();
static {
VALUE_PARSERS.put(Byte.class.getName(), new RadixableParser() {
public Object parse(String str, int radix) {
return Byte.parseByte(str, radix);
}
});
VALUE_PARSERS.put(Integer.class.getName(), new RadixableParser() {
public Object parse(String str, int radix) {
return Integer.parseInt(str, radix);
}
});
VALUE_PARSERS.put(Long.class.getName(), new RadixableParser() {
public Object parse(String str, int radix) {
return Long.parseLong(str, radix);
}
});
VALUE_PARSERS.put(Short.class.getName(), new RadixableParser() {
public Object parse(String str, int radix) {
return Short.parseShort(str, radix);
}
});
VALUE_PARSERS.put(Double.class.getName(), new ValueParser() {
public Object parse(String str) {
return Double.parseDouble(str);
}
});
VALUE_PARSERS.put(Float.class.getName(), new ValueParser() {
public Object parse(String str) {
return Float.parseFloat(str);
}
});
VALUE_PARSERS.put(Boolean.class.getName(), new ValueParser() {
public Object parse(String str) {
//The 'more obvious' implementation is too liberal and accepts anything as okay.
//return Boolean.parseBoolean(str);
str = str.toLowerCase();
if (str.equals("true")) {
return true;
} else if (str.equals("false")) {
return false;
}
throw new IllegalArgumentException("Value should be 'true' or 'false'");
}
});
}
public ValueParser getValueParser(Type type) {
ValueParser simpleParser = VALUE_PARSERS.get(type.getErasure());
if (simpleParser!=null) {
return simpleParser;
}
Collection<StsValueHint> enumValues = getAllowedValues(type, EnumCaseMode.ALIASED);
if (enumValues!=null) {
//Note, technically if 'enumValues is empty array' this means something different
// from when it is null. An empty array means a type that has no values, so
// assigning anything to it is an error.
return new EnumValueParser(niceTypeName(type), getBareValues(enumValues));
}
if (isMap(type)) {
//Trying to parse map types from scalars is not possible. Thus we
// provide a parser that allows throws
return new AlwaysFailingParser(niceTypeName(type));
}
return null;
}
private String[] getBareValues(Collection<StsValueHint> hints) {
if (hints!=null) {
String[] values = new String[hints.size()];
int i = 0;
for (StsValueHint h : hints) {
values[i++] = h.getValue();
}
return values;
}
return null;
}
/**
* @return An array of allowed values for a given type. If an array is returned then
* *only* values in the array are valid and using any other value constitutes an error.
* This may return null if allowedValues list is unknown or the type is not characterizable
* as a simple enumaration of allowed values.
* @param caseMode determines whether Enum values are returned in 'lower case form', 'orignal form',
* or 'aliased' (meaning both forms are returned).
*/
public Collection<StsValueHint> getAllowedValues(Type enumType, EnumCaseMode caseMode) {
if (enumType!=null) {
try {
String[] values = TYPE_VALUES.get(enumType.getErasure());
if (values!=null) {
if (caseMode==EnumCaseMode.ALIASED) {
ImmutableSet.Builder<String> aliased = ImmutableSet.builder();
aliased.add(values);
for (int i = 0; i < values.length; i++) {
aliased.add(values[i].toUpperCase());
}
return aliased.build().stream().map(StsValueHint::create).collect(Collectors.toList());
} else {
return Arrays.stream(values).map(StsValueHint::create).collect(Collectors.toList());
}
}
IType type = findType(enumType.getErasure());
if (type!=null && type.isEnum()) {
ImmutableList.Builder<StsValueHint> enums = ImmutableList.builder();
boolean addOriginal = caseMode == EnumCaseMode.ORIGNAL || caseMode == EnumCaseMode.ALIASED;
boolean addLowerCased = caseMode == EnumCaseMode.LOWER_CASE || caseMode == EnumCaseMode.ALIASED;
type.getFields().filter(f -> f.isEnumConstant()).forEach(f -> {
String rawName = f.getElementName();
if (addOriginal) {
enums.add(StsValueHint.create(rawName, f));
}
if (addLowerCased) {
enums.add(StsValueHint.create(StringUtil.upperCaseToHyphens(rawName), f));
}
});
return enums.build();
}
} catch (Exception e) {
Log.log(e);
}
}
return null;
}
public String niceTypeName(Type _type) {
StringBuilder buf = new StringBuilder();
niceTypeName(_type, buf);
return buf.toString();
}
public void niceTypeName(Type type, StringBuilder buf) {
if (type==null) {
buf.append("null");
return;
}
String typeStr = type.getErasure();
String primTypeName = PRIMITIVE_TYPE_NAMES.get(typeStr);
if (primTypeName!=null) {
buf.append(primTypeName);
} else if (typeStr.startsWith("java.lang.")) {
buf.append(typeStr.substring("java.lang.".length()));
} else if (typeStr.startsWith("java.util.")) {
buf.append(typeStr.substring("java.util.".length()));
} else {
buf.append(typeStr);
}
if (isEnum(type)) {
Collection<StsValueHint> values = getAllowedValues(type, EnumCaseMode.ORIGNAL);
if (values!=null && !values.isEmpty()) {
buf.append("[");
int i = 0;
for (StsValueHint hint : values) {
if (i>0) {
buf.append(", ");
}
buf.append(hint.getValue());
i++;
if (i>=4) {
break;
}
}
if (i<values.size()) {
buf.append(", ...");
}
buf.append("]");
}
} else if (type.isGeneric()) {
Type[] params = type.getParams();
buf.append("<");
for (int i = 0; i < params.length; i++) {
if (i>0) {
buf.append(", ");
}
niceTypeName(params[i], buf);
}
buf.append(">");
}
}
/**
* @return true if it is reasonable to navigate given type with '.' notation. This returns true
* by default except for some specific cases we assume are not 'dotable' such as Primitive types
* and String
*/
public boolean isDotable(Type type) {
String typeName = type.getErasure();
if (typeName.equals("java.lang.Object")) {
//special case. Treat as 'non dotable' type. This mainly for stuff like logging.level
// declared as Map<String,Object> so it would 'eat' the dots into the key.
// also it makes sense to treat Object as 'non-dotable' since we cannot determine properties
// for such an abstract type (as Object itself has no setters).
return false;
}
return !isAtomic(type);
}
public static boolean isObject(Type type) {
return type!=null && OBJECT_TYPE_NAME.equals(type.getErasure());
}
public static boolean isString(Type type) {
return type!=null && STRING_TYPE_NAME.equals(type.getErasure());
}
public boolean isAtomic(Type type) {
if (type!=null) {
String typeName = type.getErasure();
return ATOMIC_TYPES.contains(typeName) || isEnum(type);
}
return false;
}
/**
* Check if it is valid to
* use the notation <name>[<index>]=<value> in property file
* for properties of this type.
*/
public static boolean isBracketable(Type type) {
//Note array types where once not considered 'Bracketable'
//see: STS-4031
//However...
//Seems that in Boot 1.3 arrays are now 'Bracketable' and funcion much equivalnt to list (even including 'autogrowing' them).
//This is actually more logical too.
//So '[' notation in props file can be used for either list or arrays (at leats in recent versions of boot).
return isArray(type) || isList(type);
}
public static boolean isList(Type type) {
//Note: to be really correct we should use JDT infrastructure to resolve
//type in project classpath instead of using Java reflection.
//However, use reflection here is okay assuming types we care about
//are part of JRE standard libraries. Using eclipse 'type hirearchy' would
//also potentialy be very slow.
if (type!=null) {
String erasure = type.getErasure();
try {
Class<?> erasureClass = Class.forName(erasure);
return List.class.isAssignableFrom(erasureClass);
} catch (Exception e) {
//type not resolveable assume its not 'array like'
}
}
return false;
}
/**
* Check if type can be treated / represented as a sequence node in .yml file
*/
public static boolean isSequencable(Type type) {
return isList(type) || isArray(type);
}
public static boolean isArray(Type type) {
return type!=null && type.getErasure().endsWith("[]");
}
public static boolean isMap(Type type) {
//Note: to be really correct we should use JDT infrastructure to resolve
//type in project classpath instead of using Java reflection.
//However, use reflection here is okay assuming types we care about
//are part of JRE standard libraries. Using eclipse 'type hirearchy' would
//also potentialy be very slow.
if (type!=null) {
String erasure = type.getErasure();
try {
Class<?> erasureClass = Class.forName(erasure);
return Map.class.isAssignableFrom(erasureClass);
} catch (Exception e) {
//type not resolveable
}
}
return false;
}
/**
* Get domain type for a map or list generic type.
*/
public static Type getDomainType(Type type) {
if (isArray(type)) {
return getArrayDomainType(type);
} else {
return lastElement(type.getParams());
}
}
private static Type getArrayDomainType(Type type) {
if (type!=null) {
String fullName = type.getErasure();
Assert.isLegal(fullName.endsWith("[]"));
String elName = fullName.substring(0, fullName.length()-2);
return normalizePrimitiveType(new Type(elName, null));
}
return null;
}
/**
* Convert a type which is a 'primitive' type like 'int', 'long' etc. to its
* corresponding 'Boxed' type. If the type isn't a primitive type then
* just return it unchanged.
*/
private static Type normalizePrimitiveType(Type type) {
if (type!=null) {
String name = type.getErasure();
Type boxType = PRIMITIVE_TO_BOX_TYPE.get(name);
if (boxType!=null) {
return boxType;
}
}
return type;
}
public Type getKeyType(Type mapOrArrayType) {
if (isSequencable(mapOrArrayType)) {
return INTEGER_TYPE;
} else {
//assumed to be a map
return firstElement(mapOrArrayType.getParams());
}
}
public boolean isAssignableType(Type type) {
return ASSIGNABLE_TYPES.contains(type.getErasure())
|| isEnum(type)
|| isAssignableList(type);
}
private boolean isAssignableList(Type type) {
//TODO: isBracketable means 'isList' right now, but this may not be
// the case in the future.
if (isBracketable(type)) {
Type domainType = getDomainType(type);
return isAtomic(domainType);
}
return false;
}
public boolean isEnum(Type type) {
try {
IType eclipseType = findType(type.getErasure());
if (eclipseType!=null) {
return eclipseType.isEnum();
}
} catch (Exception e) {
Log.log(e);
}
return false;
}
private IType findType(String typeName) {
try {
if (javaProject!=null) {
return javaProject.findType(typeName);
}
} catch (Exception e) {
Log.log(e);
}
return null;
}
private IType findType(Type beanType) {
return findType(beanType.getErasure());
}
private static final Map<String, ValueProviderStrategy> VALUE_HINTERS = new HashMap<>();
static {
valueHints("java.nio.charset.Charset", new LazyProvider<String[]>() {
@Override
protected String[] compute() {
Set<String> charsets = Charset.availableCharsets().keySet();
return charsets.toArray(new String[charsets.size()]);
}
});
valueHints("java.util.Locale", new LazyProvider<String[]>() {
@Override
protected String[] compute() {
Locale[] locales = SimpleDateFormat.getAvailableLocales();
String[] names = new String[locales.length];
for (int i = 0; i < names.length; i++) {
names[i] = locales[i].toString();
}
return names;
}
});
valueHints("org.springframework.util.MimeType", new LazyProvider<String[]>() {
@Override
protected String[] compute() {
try {
Field f = MediaType.class.getDeclaredField("KNOWN_TYPES");
f.setAccessible(true);
@SuppressWarnings("unchecked")
Map<MediaType, MediaType> map = (Map<MediaType, MediaType>) f.get(null);
TreeSet<String> mediaTypes = new TreeSet<>();
for (MediaType m : map.keySet()) {
mediaTypes.add(m.toString());
}
return mediaTypes.toArray(new String[mediaTypes.size()]);
} catch (Exception e) {
Log.log(e);
}
return null;
}
});
valueHints("org.springframework.core.io.Resource", new ResourceHintProvider());
}
/**
* Determine properties that are setable on object of given type.
* <p>
* Note that this may return both null or an empty list, but they mean
* different things. Null means that the properties on the object are not known,
* and therefore reconciling should not check property validity. On the other hand
* returning an empty list means that there are no properties. In this case,
* accessing properties is invalid and reconciler should show an error message
* for any property access.
*
* @return A list of known properties or null if the list of properties is unknown.
*/
public List<TypedProperty> getProperties(Type type, EnumCaseMode enumMode, BeanPropertyNameMode beanMode) {
if (type==null) {
return null;
}
if (!isDotable(type)) {
//If dot navigation is not valid then really this is just like saying the type has no properties.
return Collections.emptyList();
}
if (isMap(type)) {
Type keyType = getKeyType(type);
if (keyType!=null) {
Collection<StsValueHint> keyHints = getAllowedValues(keyType, enumMode);
if (CollectionUtil.hasElements(keyHints)) {
Type valueType = getDomainType(type);
ArrayList<TypedProperty> properties = new ArrayList<>(keyHints.size());
for (StsValueHint hint : keyHints) {
String propName = hint.getValue();
properties.add(new TypedProperty(propName, valueType, hint.getDescription(), hint.getDeprecation()));
}
return properties;
}
}
} else {
String typename = type.getErasure();
IType typeFromIndex = findType(typename);
//TODO: handle type parameters.
if (typeFromIndex != null) {
ArrayList<TypedProperty> properties = new ArrayList<>();
getGetterMethods(typeFromIndex).forEach(m -> {
Deprecation deprecation = DeprecationUtil.extract(m);
Type propType = null;
try {
propType = Type.fromJavaType(m.getReturnType());
} catch (Exception e) {
Log.log(e);
}
if (beanMode.includesHyphenated()) {
properties.add(new TypedProperty(getterOrSetterNameToProperty(m.getElementName()), propType,
deprecation));
}
if (beanMode.includesCamelCase()) {
properties.add(new TypedProperty(getterOrSetterNameToCamelName(m.getElementName()), propType,
deprecation));
}
});
return properties;
}
}
return null;
}
/**
* Registers a strategy for providing value hints with a given typeName.
*/
public static void valueHints(String typeName, ValueProviderStrategy provider) {
Assert.isLegal(!VALUE_HINTERS.containsKey(typeName)); //Only one value hinter per type is supported at the moment
ATOMIC_TYPES.add(typeName); //valueHints typically implies that the type should be treated as atomic as well.
ASSIGNABLE_TYPES.add(typeName); //valueHints typically implies that the type should be treated as atomic as well.
VALUE_HINTERS.put(typeName, provider);
}
/**
* Registers a strategy for providing value hints with a given typeName.
*/
public static void valueHints(String typeName, Provider<String[]> provider) {
valueHints(typeName, new ValueProviderStrategy() {
@Override
public Flux<StsValueHint> getValues(IJavaProject javaProject, String query) {
String[] values = provider.get();
if (ArrayUtils.hasElements(values)) {
return Flux.fromArray(values)
.map(StsValueHint::create);
}
return Flux.empty();
}
});
}
private String getterOrSetterNameToProperty(String name) {
String camelName = getterOrSetterNameToCamelName(name);
return StringUtil.camelCaseToHyphens(camelName);
}
public String getterOrSetterNameToCamelName(String name) {
Assert.isLegal(name.startsWith("set") || name.startsWith("get") || name.startsWith("is"));
int prefixLen = name.startsWith("is") ? 2 : 3;
String camelName = Character.toLowerCase(name.charAt(prefixLen)) + name.substring(prefixLen+1);
return camelName;
}
private Stream<IMethod> getGetterMethods(IType eclipseType) {
if (eclipseType != null && eclipseType.isClass()) {
return eclipseType.getMethods().filter(m -> {
if (!isStatic(m) && isPublic(m)) {
String mname = m.getElementName();
if ((mname.startsWith("get") && mname.length() >= 4)
|| (mname.startsWith("is") && mname.length() >= 3)) {
// Need at least x chars or the property name will be
// empty.
if (m.parameters().count() == 0) {
return true;
}
}
}
return false;
});
}
return Stream.empty();
}
// private List<IMethod> getSetterMethods(IType eclipseType) {
// try {
// if (eclipseType!=null && eclipseType.isClass()) {
// IMethod[] allMethods = eclipseType.getMethods();
// if (ArrayUtils.hasElements(allMethods)) {
// ArrayList<IMethod> setters = new ArrayList<IMethod>();
// for (IMethod m : allMethods) {
// String mname = m.getElementName();
// if (mname.startsWith("set") && mname.length()>=4) {
// //Need at least 4 chars or the property name will be empty.
// String sig = m.getSignature();
// int numParams = Signature.getParameterCount(sig);
// if (numParams==1) {
// setters.add(m);
// }
// }
// }
// return setters;
// }
// }
// } catch (Exception e) {
// BootActivator.log(e);
// }
// return null;
// }
private boolean isStatic(IMethod m) {
try {
return Flags.isStatic(m.getFlags());
} catch (Exception e) {
//Couldn't determine if it was public or not... let's assume it was NOT
// (will result in potentially more CA completions)
Log.log(e);
return false;
}
}
private boolean isPublic(IMethod m) {
try {
return m.getDeclaringType().isInterface()
|| Flags.isPublic(m.getFlags());
} catch (Exception e) {
//Couldn't determine if it was public or not... let's assume it WAS
// (will result in potentially more CA completions)
Log.log(e);
return true;
}
}
public Map<String, TypedProperty> getPropertiesMap(Type type, EnumCaseMode enumMode, BeanPropertyNameMode beanMode) {
//TODO: optimize, produce directly as a map instead of
// first creating list and then coverting it.
List<TypedProperty> list = getProperties(type, enumMode, beanMode);
if (list!=null) {
Map<String, TypedProperty> map = new HashMap<>();
for (TypedProperty p : list) {
map.put(p.getName(), p);
}
return map;
}
return null;
}
/**
* Maybe ne null in some contexts. In such context functionality will be limited because
* types can not be resolved.
*/
public IJavaProject getJavaProject() {
return javaProject;
}
public IField getField(Type beanType, String propName) {
IType type = findType(beanType);
return getExactField(type, propName);
}
protected IField getExactField(IType type, String fieldName) {
IField f = type.getField(StringUtil.hyphensToCamelCase(fieldName, false));
if (f!=null && f.exists()) {
return f;
}
return null;
}
public IField getEnumConstant(Type enumType, String propName) {
IType type = findType(enumType);
//1: if propname is already spelled exactly...
IField f = getExactField(type, propName);
if (f!=null) return f;
//2: most likely enum constant is upper-case form of propname
String fieldName = StringUtil.hyphensToUpperCase(propName);
return getExactField(type, fieldName);
}
public Optional<IMethod> getSetter(Type beanType, String propName) {
try {
String setterName = "set" + StringUtil.hyphensToCamelCase(propName, true);
IType type = findType(beanType);
return type.getMethods().filter(m -> setterName.equals(m.getElementName())).findFirst();
} catch (Exception e) {
Log.log(e);
}
return Optional.empty();
}
public IJavaElement getGetter(Type beanType, String propName) {
String getterName = "get" + StringUtil.hyphensToCamelCase(propName, true);
IType type = findType(beanType);
IMethod m = type.getMethod(getterName, Stream.empty());
if (m.exists()) {
return m;
}
return null;
}
public static String deprecatedPropertyMessage(String name, String contextType, String replace, String reason) {
StringBuilder msg = new StringBuilder("Property '"+name+"'");
if (StringUtil.hasText(contextType)) {
msg.append(" of type '"+contextType+"'");
}
boolean hasReplace = StringUtil.hasText(replace);
boolean hasReason = StringUtil.hasText(reason);
if (!hasReplace && !hasReason) {
msg.append(" is Deprecated!");
} else {
msg.append(" is Deprecated: ");
if (hasReplace) {
msg.append("Use '"+ replace +"' instead.");
if (hasReason) {
msg.append(" Reason: ");
}
}
if (hasReason) {
msg.append(reason);
}
}
return msg.toString();
}
public Collection<StsValueHint> getHintValues(Type type, String query, EnumCaseMode enumCaseMode) {
if (type!=null) {
Collection<StsValueHint> allowed = getAllowedValues(type, enumCaseMode);
if (allowed!=null) {
return allowed;
}
ValueProviderStrategy valueHinter = VALUE_HINTERS.get(type.getErasure());
if (valueHinter!=null) {
return valueHinter.getValuesNow(javaProject, query);
}
}
return null;
}
/**
* Determine the dimensionality of a collection-like type (i.e. a Map or List). The dimensionality
* is essentialy how many succesive 'indexing' operations need to be applied before reasing the actual elements.
* <p>
* For examle:
* List<String> -> 1
* List<List<String>> -> 2
* List<List<List<String>>> -> 2
* Map<*,List<String>> -> 2
*/
public static int getDimensionality(Type type) {
int dim = 0;
while (isSequencable(type) || isMap(type)) {
dim++;
type = getDomainType(type);
}
return dim;
}
public static boolean isClass(Type type) {
if (type!=null) {
return CLASS_TYPE_NAME.equals(type.getErasure());
}
return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// Addapting our interface so it is compatible with YTypeUtil
//
// This allows our types to be used by the more generic stuff from the 'editor.support' plugin.
//
// Note, it may be possible to avoid having these 'adaptor' methods by making YTypeUtil a paramerized
// type. I.e something like "interface YTypeUtil<T extends YType>.
// Paramterizations like that tend to propagate fire and wide in the code and make for complicated
// signatures. For now using these bredging methods is simpler if perhaps a bit more error prone.
// @Override
// public boolean isAtomic(YType type) {
// return isAtomic((Type)type);
// }
//
// @Override
// public boolean isMap(YType type) {
// return isMap((Type)type);
// }
//
// @Override
// public boolean isSequencable(YType type) {
// return isSequencable((Type)type);
// }
//
// @Override
// public YType getDomainType(YType type) {
// return getDomainType((Type)type);
// }
//
// @Override
// public String[] getHintValues(YType type) {
// return getAllowedValues((Type) type, EnumCaseMode.ALIASED);
// }
//
// @SuppressWarnings("unchecked")
// @Override
// public List<YTypedProperty> getProperties(YType type) {
// //Dirty hack, passing this through a raw type to bypass the java type system
// //complaining the List<TypedProperty> is not compatible with List<YTypedProperty>
// //This dirty and 'illegal' conversion is okay because the list is only used for reading.
// @SuppressWarnings("rawtypes")
// List props = getProperties((Type)type, EnumCaseMode.ALIASED, BeanPropertyNameMode.ALIASED);
// return Collections.unmodifiableList(props);
// }
//
// @SuppressWarnings("unchecked")
// @Override
// public Map<String, YTypedProperty> getPropertiesMap(YType type) {
// //Dirty hack, see comment in getProperties(YType)
// @SuppressWarnings("rawtypes")
// Map map = getPropertiesMap((Type)type, EnumCaseMode.ALIASED, BeanPropertyNameMode.ALIASED);
// return Collections.unmodifiableMap(map);
// }
//
// @Override
// public String niceTypeName(YType type) {
// return niceTypeName((Type)type);
// }
//
// @Override
// public YType getKeyType(YType type) {
// return getKeyType((Type)type);
// }
}

View File

@@ -0,0 +1,8 @@
package org.springframework.ide.vscode.boot.metadata.types;
import org.springframework.ide.vscode.commons.util.IDocument;
@FunctionalInterface
public interface TypeUtilProvider {
TypeUtil getTypeUtil(IDocument doc);
}

View File

@@ -0,0 +1,95 @@
package org.springframework.ide.vscode.boot.metadata.types;
import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
/**
* Represents a property on a Type that can be accessed by name.
*
* @author Kris De Volder
*/
public class TypedProperty implements YTypedProperty {
/**
* The name of the property
*/
private final String name;
/**
* The type of value associated with the property.
*/
private final Type type;
/**
* Provides a description for this property.
*/
private final Renderable descriptionProvider;
private final Deprecation deprecation;
public TypedProperty(String name, Type type, Deprecation deprecation) {
this(name, type, Renderables.NO_DESCRIPTION, deprecation);
}
public TypedProperty(String name, Type type, Renderable descriptionProvider, Deprecation deprecation) {
this.name = name;
this.type = type;
this.descriptionProvider = descriptionProvider;
this.deprecation = deprecation;
}
public String getName() {
return name;
}
public Type getType() {
return type;
}
@Override
public String toString() {
return name + "::" + type;
}
@Override
public Renderable getDescription() {
//TODO: real implementation that somehow gets this from somewhere (i.e. the JavaDoc)
// Note that presently the application.yml and application.properties editor do not actually
// use this description provider but produce hover infos in a different way (so this is only
// used in Schema-based content assist, reconciling and hovering.
//So in that sense putting a good implementation here is kind of pointless right now.
//More refactoring needs to be done to also make use of this.
return descriptionProvider;
}
public static Type typeOf(TypedProperty typedProperty) {
if (typedProperty!=null) {
return typedProperty.getType();
}
return null;
}
public boolean isDeprecated() {
return deprecation!=null;
}
public String getDeprecationReplacement() {
if (deprecation!=null) {
return deprecation.getReplacement();
}
return null;
}
public String getDeprecationReason() {
if (deprecation!=null) {
return deprecation.getReason();
}
return null;
}
public Deprecation getDeprecation() {
return deprecation;
}
}

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

@@ -8,20 +8,20 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.application.properties.metadata.hints.HintProvider;
import org.springframework.ide.vscode.application.properties.metadata.hints.HintProviders;
import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.application.properties.metadata.hints.ValueHintHoverInfo;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap.Match;
import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.hints.HintProvider;
import org.springframework.ide.vscode.boot.metadata.hints.HintProviders;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.boot.metadata.hints.ValueHintHoverInfo;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeParser;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap.Match;
import org.springframework.ide.vscode.boot.properties.reconcile.PropertyNavigator;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;

View File

@@ -2,9 +2,9 @@ package org.springframework.ide.vscode.boot.properties.completions;
import java.util.Collection;
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;

View File

@@ -11,14 +11,14 @@ import static org.springframework.ide.vscode.commons.util.Renderables.text;
import java.util.Collection;
import java.util.Optional;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndex;
import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.boot.common.InformationTemplates;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndex;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.util.BadLocationException;

View File

@@ -1,7 +1,7 @@
package org.springframework.ide.vscode.boot.properties.hover;
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.util.IDocument;

View File

@@ -3,8 +3,8 @@ package org.springframework.ide.vscode.boot.properties.reconcile;
import java.util.Arrays;
import java.util.regex.Pattern;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;

View File

@@ -1,15 +1,15 @@
package org.springframework.ide.vscode.boot.properties.reconcile;
import static org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.isBracketable;
import static org.springframework.ide.vscode.boot.metadata.types.TypeUtil.isBracketable;
import static org.springframework.ide.vscode.boot.properties.reconcile.SpringPropertyProblem.problem;
import java.util.List;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;

View File

@@ -18,14 +18,14 @@ import static org.springframework.ide.vscode.commons.util.StringUtil.commonPrefi
import java.util.regex.Pattern;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndex;
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
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.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeParser;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.boot.properties.quickfix.ReplaceDeprecatedPropertyQuickfix;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;

View File

@@ -1,6 +1,6 @@
package org.springframework.ide.vscode.boot.properties.reconcile;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.commons.languageserver.quickfix.ProblemFixer;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;

View File

@@ -19,22 +19,22 @@ import java.util.Map;
import java.util.Set;
import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.ide.vscode.application.properties.metadata.IndexNavigator;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.application.properties.metadata.hints.HintProvider;
import org.springframework.ide.vscode.application.properties.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.application.properties.metadata.hints.ValueHintHoverInfo;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap.Match;
import org.springframework.ide.vscode.boot.common.InformationTemplates;
import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory;
import org.springframework.ide.vscode.boot.common.RelaxedNameConfig;
import org.springframework.ide.vscode.boot.metadata.IndexNavigator;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.hints.HintProvider;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.boot.metadata.hints.ValueHintHoverInfo;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeParser;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap.Match;
import org.springframework.ide.vscode.commons.java.IField;
import org.springframework.ide.vscode.commons.java.IJavaElement;
import org.springframework.ide.vscode.commons.java.IMember;

View File

@@ -10,15 +10,15 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.ide.vscode.application.properties.metadata.IndexNavigator;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.application.properties.metadata.types.Type;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeParser;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.boot.metadata.IndexNavigator;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.types.Type;
import org.springframework.ide.vscode.boot.metadata.types.TypeParser;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
import org.springframework.ide.vscode.boot.metadata.types.TypedProperty;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.BeanPropertyNameMode;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil.EnumCaseMode;
import org.springframework.ide.vscode.boot.yaml.quickfix.ReplaceDeprecatedYamlQuickfix;
import org.springframework.ide.vscode.application.properties.metadata.types.TypedProperty;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.ValueParser;

View File

@@ -12,11 +12,11 @@ package org.springframework.ide.vscode.boot.yaml.reconcile;
import static org.springframework.ide.vscode.boot.yaml.reconcile.ApplicationYamlProblems.Type.YAML_SYNTAX_ERROR;
import org.springframework.ide.vscode.application.properties.metadata.IndexNavigator;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.application.properties.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.application.properties.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.application.properties.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.boot.metadata.IndexNavigator;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.commons.util.IDocument;

View File

@@ -1,6 +1,6 @@
package org.springframework.ide.vscode.boot.yaml.reconcile;
import org.springframework.ide.vscode.application.properties.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.commons.languageserver.quickfix.ProblemFixer;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;