Boot properties metadata excluding JDT dependencies

This commit is contained in:
BoykoAlex
2016-10-06 15:26:04 -04:00
parent d4439b246b
commit ff23aca698
34 changed files with 4704 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
/.classpath
/.project

View File

@@ -0,0 +1,5 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.8

View File

@@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

View File

@@ -0,0 +1,51 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>commons-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>application-properties-metadata</artifactId>
<description>Builds metadata for boot application properties based on project's classpath contents</description>
<dependencies>
<!-- Guava collections -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<!-- Javax Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Reactor -->
<!--
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.0.2.RELEASE</version>
</dependency>
-->
<!-- Common Utilities -->
<dependency>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>util-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Eclipse JDT -->
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>core</artifactId>
<version>3.3.0-v_771</version>
</dependency>
</dependencies>
</project>

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

@@ -0,0 +1,164 @@
package org.springframework.ide.vscode.boot.properties.metadata;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepositoryJsonBuilder;
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(Path projectPath) {
return load(projectPath.resolve("classpath.txt"), projectPath.resolve("target/classes"));
}
private ConfigurationMetadataRepository load(Path classPathFilePath, Path outputFolderPath) {
loadFromClasspath(classPathFilePath);
loadFromOutputFolder(outputFolderPath);
ConfigurationMetadataRepository repository = builder.build();
return repository;
}
private void loadFromClasspath(Path classPathFilePath) {
if (classPathFilePath != null && Files.exists(classPathFilePath)) {
readClassPathFile(classPathFilePath).ifPresent(classPathSet -> {
classPathSet.stream().forEach(this::loadFromJar);
});
}
}
private Optional<Set<Path>> readClassPathFile(Path classPathFilePath){
try {
InputStream in = Files.newInputStream(classPathFilePath);
String text = new BufferedReader(new InputStreamReader(in)).lines().collect(Collectors.joining());
Path dir = classPathFilePath.getParent();
return Optional.of(Arrays.stream(text.split(File.pathSeparator)).map(dir::resolve).collect(Collectors.toSet()));
} catch (IOException e) {
LOG.log(Level.SEVERE, "Failed to read classpath text file", e);
return Optional.empty();
}
}
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);
}
public static void main(String[] args) {
if (args.length > 0) {
Path projectPath = Paths.get(args[0]);
ConfigurationMetadataRepository repo = new PropertiesLoader().load(projectPath);
Map<String, ConfigurationMetadataProperty> allProperties = repo.getAllProperties();
allProperties.keySet().forEach(System.out::println);
}
}
}

View File

@@ -0,0 +1,217 @@
/*******************************************************************************
* 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.properties.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.properties.metadata.ValueProviderRegistry.ValueProviderStrategy;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
/**
* Information about a spring property, basically, this is the same as
*
* {@link ConfigurationMetadataProperty} but augmented with information
* about {@link ConfigurationMetadataSource}s that declare the property.
*
* @author Kris De Volder
*/
public class PropertyInfo {
/**
* Identifies a 'Source'. This is essentially the sames as {@link ConfigurationMetadataSource}.
* We could use {@link ConfigurationMetadataSource} directly, but this only contains
* the info that we actually use so takes less memory.
*/
public static class PropertySource {
private final String sourceType;
private final String sourceMethod;
public PropertySource(ConfigurationMetadataSource source) {
String st = source.getSourceType();
this.sourceType = st!=null?st:source.getType();
this.sourceMethod = source.getSourceMethod();
}
@Override
public String toString() {
return sourceType+"::"+sourceMethod;
}
public String getSourceType() {
return sourceType;
}
public String getSourceMethod() {
return sourceMethod;
}
}
final private String id;
private String type;
final private String name;
final private Object defaultValue;
final private String description;
private List<PropertySource> sources;
private Deprecation deprecation;
private ImmutableList<ValueHint> valueHints;
private ImmutableList<ValueHint> keyHints;
private ValueProviderStrategy valueProvider;
private ValueProviderStrategy keyProvider;
public PropertyInfo(String id, String type, String name,
Object defaultValue, String description,
Deprecation deprecation,
List<ValueHint> valueHints,
List<ValueHint> keyHints,
ValueProviderStrategy valueProvider,
ValueProviderStrategy keyProvider,
List<PropertySource> sources) {
super();
this.id = id;
this.type = type;
this.name = name;
this.defaultValue = defaultValue;
this.description = description;
this.deprecation = deprecation;
this.valueHints = valueHints==null?null:ImmutableList.copyOf(valueHints);
this.keyHints = keyHints==null?null:ImmutableList.copyOf(keyHints);
this.valueProvider = valueProvider;
this.keyProvider = keyProvider;
this.sources = sources;
}
public PropertyInfo(ValueProviderRegistry valueProviders, ConfigurationMetadataProperty prop) {
this(
prop.getId(),
prop.getType(),
prop.getName(),
prop.getDefaultValue(),
prop.getDescription(),
prop.getDeprecation(),
prop.getHints().getValueHints(),
prop.getHints().getKeyHints(),
valueProviders.resolve(prop.getHints().getValueProviders()),
valueProviders.resolve(prop.getHints().getKeyProviders()),
null
);
for (ValueProvider h : prop.getHints().getValueProviders()) {
if (h.getName().equals("handle-as")) {
handleAs(h.getParameters().get("target"));
}
}
}
private void handleAs(Object targetObject) {
// debug("handle-as "+this.getId()+" -> "+targetObject);
if (targetObject instanceof String) {
this.type = (String)targetObject;
}
}
public String getId() {
return id;
}
public String getType() {
return type;
}
public String getName() {
return name;
}
public Object getDefaultValue() {
return defaultValue;
}
public String getDescription() {
return description;
}
// public 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,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.properties.metadata;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import org.springframework.ide.vscode.boot.properties.util.FuzzyMap;
import org.springframework.ide.vscode.boot.properties.util.Listener;
import org.springframework.ide.vscode.boot.properties.util.ListenerManager;
/**
* 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<Path, SpringPropertyIndex> indexes = null;
final private ValueProviderRegistry valueProviders;
public SpringPropertiesIndexManager(ValueProviderRegistry valueProviders) {
this.valueProviders = valueProviders;
}
public synchronized FuzzyMap<PropertyInfo> get(Path projectFolder) {
if (indexes==null) {
indexes = new HashMap<>();
}
SpringPropertyIndex index = indexes.get(projectFolder);
if (index==null) {
index = new SpringPropertyIndex(valueProviders, projectFolder);
indexes.put(projectFolder, 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,128 @@
/*******************************************************************************
* 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.properties.metadata;
import java.nio.file.Path;
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.properties.util.FuzzyMap;
public class SpringPropertyIndex extends FuzzyMap<PropertyInfo> {
private ValueProviderRegistry valueProviders;
public SpringPropertyIndex(ValueProviderRegistry valueProviders, Path 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();
}
}

View File

@@ -0,0 +1,93 @@
/*******************************************************************************
* 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.properties.metadata;
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.properties.util.CollectionUtil;
/**
* 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,48 @@
/*******************************************************************************
* 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.properties.util;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @author Kris De Volder
*/
public class ArrayUtils {
public static <T> boolean hasElements(T[] arr) {
return arr!=null && arr.length>0;
}
public static <T> T lastElement(T[] arr) {
if (hasElements(arr)) {
return arr[arr.length-1];
}
return null;
}
public static <T> T firstElement(T[] arr) {
if (hasElements(arr)) {
return arr[0];
}
return null;
}
@SuppressWarnings("unchecked")
public static <T> T[] remove(T[] array, T element) {
ArrayList<T> toKeep = new ArrayList<>(Arrays.asList(array));
toKeep.remove(element);
T[] newArray =(T[]) Array.newInstance(array.getClass().getComponentType(), toKeep.size());
return toKeep.toArray(newArray);
}
}

View File

@@ -0,0 +1,24 @@
/*******************************************************************************
* 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.properties.util;
import java.util.Collection;
/**
* @author Kris De Volder
*/
public class CollectionUtil {
public static <E> boolean hasElements(Collection<E> c) {
return c!=null && !c.isEmpty();
}
}

View File

@@ -0,0 +1,169 @@
/*******************************************************************************
* 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.properties.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.util.FuzzyMatcher;
/**
* 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.properties.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.properties.util;
import java.util.Arrays;
import org.springframework.ide.vscode.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

@@ -0,0 +1,132 @@
/*******************************************************************************
* 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.properties.util;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StringUtil {
public static boolean hasText(String name) {
return name!=null && !name.trim().equals("");
}
public static String trim(String s) {
if (s!=null) {
return s.trim();
}
return null;
}
public static String trimEnd(String s) {
if (s!=null) {
return s.replaceAll("\\s+\\z", "");
}
return null;
}
public static int commonPrefixLength(CharSequence s, CharSequence t) {
int shortestStringLen = Math.min(s.length(), t.length());
for (int i = 0; i < shortestStringLen; i++) {
if (s.charAt(i)!=t.charAt(i)) {
return i;
}
}
//no difference found upto entire length of shortest string.
return shortestStringLen;
}
/**
* @return longest string which is a prefix of both argument Strings.
*/
public static String commonPrefix(CharSequence s, CharSequence t) {
int len = commonPrefixLength(s, t);
if (len>0) {
return s.subSequence(0,len).toString();
}
return "";
}
public static String camelCaseToHyphens(String value) {
Matcher matcher = CAMEL_CASE_PATTERN.matcher(value);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(result, matcher.group(1) + '-'
+ matcher.group(2).toLowerCase());
}
matcher.appendTail(result);
return result.toString();
}
private static final Pattern CAMEL_CASE_PATTERN = Pattern.compile("([^A-Z-])([A-Z])");
public static String arrayToCommaDelimitedString(Object[] array) {
return collectionToCommaDelimitedString(Arrays.asList(array));
}
public static String collectionToCommaDelimitedString(Collection<?> items) {
StringBuilder buf = new StringBuilder();
boolean first = true;
for (Object item : items) {
if (!first) {
buf.append(",");
}
buf.append(item);
first = false;
}
return buf.toString();
}
public static String upperCaseToHyphens(String v) {
if (v!=null) {
return v.toLowerCase().replace('_', '-');
}
return null;
}
public static String hyphensToUpperCase(String v) {
if (v!=null) {
return v.toUpperCase().replace('-', '_');
}
return null;
}
public static String hyphensToCamelCase(String propName, boolean startWithUpperCase) {
String [] parts = propName.split("-");
if (startWithUpperCase) {
parts[0] = upCaseFirstChar(parts[0]);
}
StringBuilder camelCased = new StringBuilder(parts[0]);
for (int i = 1; i < parts.length; i++) {
camelCased.append(upCaseFirstChar(parts[i]));
}
return camelCased.toString();
}
public static String upCaseFirstChar(String string) {
if (StringUtil.hasText(string)) {
return Character.toUpperCase(string.charAt(0)) + string.substring(1);
}
return "";
}
public static String datestamp() {
Date d = new Date();
SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd");
return f.format(d);
}
}

View File

@@ -0,0 +1,250 @@
/*******************************************************************************
* Copyright (c) 2004, 2016 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* This class is a thread safe list that is designed for storing lists of listeners.
* The implementation is optimized for minimal memory footprint, frequent reads
* and infrequent writes. Modification of the list is synchronized and relatively
* expensive, while accessing the listeners is very fast. For legacy code, readers are given access
* to the underlying array data structure for reading, with the trust that they will
* not modify the underlying array.
* <p>
* <a name="same"></a>A listener list handles the <i>same</i> listener being added
* multiple times, and tolerates removal of listeners that are the same as other
* listeners in the list. For this purpose, listeners can be compared with each other
* using either equality or identity, as specified in the list constructor.
* </p>
* <p>
* Use an enhanced 'for' loop to notify listeners. The recommended
* code sequence for notifying all registered listeners of say,
* <code>FooListener#eventHappened(Event)</code>, is:
* </p>
* <pre>
ListenerList&lt;FooListener&gt; fooListeners = new ListenerList<>();
//...
for (FooListener listener : fooListeners) {
listener.eventHappened(event);
}
* </pre>
* <p>
* Legacy code may still call {@link #getListeners()} and then use a 'for' loop
* to iterate the {@code Object[]}. This might be insignificantly faster, but
* it lacks type-safety and risks inadvertent modifications to the array.
* </p>
* <p>
* This class can be used without OSGi running.
* </p>
*
* @param <E> the type of listeners in this list
* @since org.eclipse.equinox.common 3.2
*/
public class ListenerList<E> implements Iterable<E> {
/**
* The empty array singleton instance.
*/
private static final Object[] EmptyArray = new Object[0];
/**
* Mode constant (value 0) indicating that listeners should be considered
* the <a href="ListenerList.html#same">same</a> if they are equal.
*/
public static final int EQUALITY = 0;
/**
* Mode constant (value 1) indicating that listeners should be considered
* the <a href="ListenerList.html#same">same</a> if they are identical.
*/
public static final int IDENTITY = 1;
/**
* Indicates the comparison mode used to determine if two
* listeners are equivalent
*/
private final boolean identity;
/**
* The list of listeners. Initially empty but initialized
* to an array of size capacity the first time a listener is added.
* Maintains invariant: listeners != null
*/
private volatile Object[] listeners = EmptyArray;
/**
* Creates a listener list in which listeners are compared using equality.
*/
public ListenerList() {
this(EQUALITY);
}
/**
* Creates a listener list using the provided comparison mode.
*
* @param mode The mode used to determine if listeners are the <a href="ListenerList.html#same">same</a>.
*/
public ListenerList(int mode) {
if (mode != EQUALITY && mode != IDENTITY)
throw new IllegalArgumentException();
this.identity = mode == IDENTITY;
}
/**
* Adds a listener to this list. This method has no effect if the <a href="ListenerList.html#same">same</a>
* listener is already registered.
*
* @param listener the non-<code>null</code> listener to add
*/
public synchronized void add(E listener) {
// This method is synchronized to protect against multiple threads adding
// or removing listeners concurrently. This does not block concurrent readers.
if (listener == null)
throw new IllegalArgumentException();
// check for duplicates
final int oldSize = listeners.length;
for (int i = 0; i < oldSize; ++i) {
Object listener2 = listeners[i];
if (identity ? listener == listener2 : listener.equals(listener2))
return;
}
// Thread safety: create new array to avoid affecting concurrent readers
Object[] newListeners = new Object[oldSize + 1];
System.arraycopy(listeners, 0, newListeners, 0, oldSize);
newListeners[oldSize] = listener;
//atomic assignment
this.listeners = newListeners;
}
/**
* Returns an array containing all the registered listeners.
* The resulting array is unaffected by subsequent adds or removes.
* If there are no listeners registered, the result is an empty array.
* Use this method when notifying listeners, so that any modifications
* to the listener list during the notification will have no effect on
* the notification itself.
* <p>
* Note: Callers of this method <b>must not</b> modify the returned array.
* </p>
* <p>
* Note: The recommended and type-safe way to iterate this list is to use
* an enhanced 'for' statement, see {@link ListenerList}.
* This method is deprecated for new code.
* </p>
*
* @return the list of registered listeners
*/
public Object[] getListeners() {
return listeners;
}
/**
* Returns an iterator over all the registered listeners.
* The resulting iterator is unaffected by subsequent adds or removes.
* Use this method when notifying listeners, so that any modifications
* to the listener list during the notification will have no effect on
* the notification itself.
*
* @return an iterator
* @since org.eclipse.equinox.common 3.8
*/
@Override
public Iterator<E> iterator() {
return new ListenerListIterator<>(listeners);
}
private static class ListenerListIterator<E> implements Iterator<E> {
private Object[] listeners;
private int i;
public ListenerListIterator(Object[] listeners) {
this.listeners = listeners;
}
@Override
public boolean hasNext() {
return i < listeners.length;
}
@Override
public E next() {
if (i >= listeners.length) {
throw new NoSuchElementException();
}
@SuppressWarnings("unchecked") // (E) is safe, because #add(E) only accepts Es
E next = (E) listeners[i++];
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
/**
* Returns whether this listener list is empty.
*
* @return <code>true</code> if there are no registered listeners, and
* <code>false</code> otherwise
*/
public boolean isEmpty() {
return listeners.length == 0;
}
/**
* Removes a listener from this list. Has no effect if the <a href="ListenerList.html#same">same</a>
* listener was not already registered.
*
* @param listener the non-<code>null</code> listener to remove
*/
public synchronized void remove(Object listener) {
// This method is synchronized to protect against multiple threads adding
// or removing listeners concurrently. This does not block concurrent readers.
if (listener == null)
throw new IllegalArgumentException();
int oldSize = listeners.length;
for (int i = 0; i < oldSize; ++i) {
Object listener2 = listeners[i];
if (identity ? listener == listener2 : listener.equals(listener2)) {
if (oldSize == 1) {
listeners = EmptyArray;
} else {
// Thread safety: create new array to avoid affecting concurrent readers
Object[] newListeners = new Object[oldSize - 1];
System.arraycopy(listeners, 0, newListeners, 0, i);
System.arraycopy(listeners, i + 1, newListeners, i, oldSize - i - 1);
//atomic assignment to field
this.listeners = newListeners;
}
return;
}
}
}
/**
* Returns the number of registered listeners.
*
* @return the number of registered listeners
*/
public int size() {
return listeners.length;
}
/**
* Removes all listeners from this list.
*/
public synchronized void clear() {
listeners = EmptyArray;
}
}