Polish ConfigurationPropertySource support

Improve ConfigurationPropertySource support by reworking some of the
stream calls based on advice offered by Tagir Valeev from JetBrains.

Also improved ConfigurationPropertySource.containsDescendantOf so that
it returns an enum rather than an Optional<Boolean> (again based on
feedback from Tagir).

See gh-9000
pull/9145/head
Phillip Webb 8 years ago
parent fa4de13519
commit d969ebad07

@ -36,6 +36,7 @@ import org.springframework.boot.context.properties.source.ConfigurationProperty;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.ConfigurationPropertyState;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.Environment;
import org.springframework.format.support.DefaultFormattingConversionService;
@ -309,8 +310,8 @@ public class Binder {
private boolean isUnbindableBean(ConfigurationPropertyName name, Bindable<?> target,
Context context) {
if (context.streamSources().map((s) -> s.containsDescendantOf(name).orElse(false))
.anyMatch(Boolean.TRUE::equals)) {
if (context.streamSources().anyMatch((s) -> s
.containsDescendantOf(name) == ConfigurationPropertyState.PRESENT)) {
// We know there are properties to bind so we can't bypass anything
return false;
}
@ -324,8 +325,8 @@ public class Binder {
private boolean containsNoDescendantOf(Stream<ConfigurationPropertySource> sources,
ConfigurationPropertyName name) {
return sources.map((s) -> s.containsDescendantOf(name).orElse(true))
.allMatch(Boolean.FALSE::equals);
return sources.allMatch(
(s) -> s.containsDescendantOf(name) == ConfigurationPropertyState.ABSENT);
}
/**

@ -27,6 +27,7 @@ import java.util.function.Supplier;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyState;
import org.springframework.core.ResolvableType;
/**
@ -40,9 +41,8 @@ class JavaBeanBinder implements BeanBinder {
@Override
public <T> T bind(ConfigurationPropertyName name, Bindable<T> target,
BindContext context, BeanPropertyBinder propertyBinder) {
boolean hasKnownBindableProperties = context.streamSources()
.map((s) -> s.containsDescendantOf(name).orElse(false))
.anyMatch(Boolean.TRUE::equals);
boolean hasKnownBindableProperties = context.streamSources().anyMatch((
s) -> s.containsDescendantOf(name) == ConfigurationPropertyState.PRESENT);
Bean<T> bean = Bean.get(target, hasKnownBindableProperties);
if (bean == null) {
return null;

@ -16,8 +16,6 @@
package org.springframework.boot.context.properties.source;
import java.util.Optional;
import org.springframework.util.Assert;
/**
@ -53,14 +51,21 @@ class AliasedConfigurationPropertySource implements ConfigurationPropertySource
}
@Override
public Optional<Boolean> containsDescendantOf(ConfigurationPropertyName name) {
public ConfigurationPropertyState containsDescendantOf(
ConfigurationPropertyName name) {
Assert.notNull(name, "Name must not be null");
Optional<Boolean> result = this.source.containsDescendantOf(name);
ConfigurationPropertyState result = this.source.containsDescendantOf(name);
if (result != ConfigurationPropertyState.ABSENT) {
return result;
}
for (ConfigurationPropertyName alias : getAliases().getAliases(name)) {
Optional<Boolean> aliasResult = this.source.containsDescendantOf(alias);
result = result.flatMap((r) -> aliasResult.flatMap(a -> Optional.of(r || a)));
ConfigurationPropertyState aliasResult = this.source
.containsDescendantOf(alias);
if (aliasResult != ConfigurationPropertyState.ABSENT) {
return aliasResult;
}
return result;
}
return ConfigurationPropertyState.ABSENT;
}
protected ConfigurationPropertySource getSource() {

@ -16,7 +16,6 @@
package org.springframework.boot.context.properties.source;
import java.util.Optional;
import java.util.function.Predicate;
import org.springframework.boot.origin.OriginTrackedValue;
@ -42,25 +41,15 @@ public interface ConfigurationPropertySource {
ConfigurationProperty getConfigurationProperty(ConfigurationPropertyName name);
/**
* Optionally returns if the source contains any descendants of the specified name.
* <ul>
* <li>A result of {@code true} means that there is at least on property in the source
* with a name that's an
* {@link ConfigurationPropertyName#isAncestorOf(ConfigurationPropertyName) ancestor}
* of {@code name}.</li>
* <li>A result of {@code false} means that that there are no properties in the source
* with a name that's an
* {@link ConfigurationPropertyName#isAncestorOf(ConfigurationPropertyName) ancestor}
* of {@code name}.</li>
* <li>A result of {@code empty} means it is not possible to determine up determine if
* there's a property in the source with a name that's an
* {@link ConfigurationPropertyName#isAncestorOf(ConfigurationPropertyName) ancestor}
* of {@code name}.
* </ul>
* Returns if the source contains any descendants of the specified name. May return
* {@link ConfigurationPropertyState#PRESENT} or
* {@link ConfigurationPropertyState#ABSENT} if an answer can be determined or
* {@link ConfigurationPropertyState#UNKNOWN} if it's not possible to determine a
* definitive answer.
* @param name the name to check
* @return an optional boolean determining if a descendant is contained in the source
* @return if the source contains any descendants
*/
Optional<Boolean> containsDescendantOf(ConfigurationPropertyName name);
ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name);
/**
* Return a filtered variant of this source, containing only names that match the

@ -0,0 +1,70 @@
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.function.Predicate;
import org.springframework.util.Assert;
/**
* The state of content from a {@link ConfigurationPropertySource}.
*
* @author Phillip Webb
* @since 2.0.0
*/
public enum ConfigurationPropertyState {
/**
* The {@link ConfigurationPropertySource} has at least one matching
* {@link ConfigurationProperty}.
*/
PRESENT,
/**
* The {@link ConfigurationPropertySource} has no matching
* {@link ConfigurationProperty ConfigurationProperties}.
*/
ABSENT,
/**
* It's not possible to determine if {@link ConfigurationPropertySource} has matching
* {@link ConfigurationProperty ConfigurationProperties} or not.
*/
UNKNOWN;
/**
* Search the given iterable using a predicate to determine if content is
* {@link #PRESENT} or {@link #ABSENT}.
* @param <T> the data type
* @param source the source iterable to search
* @param predicate the predicate used to test for presence
* @return {@link #PRESENT} if the iterable contains a matching item, otherwise
* {@link #ABSENT}.
*/
static <T> ConfigurationPropertyState search(Iterable<T> source,
Predicate<T> predicate) {
Assert.notNull(source, "Source must not be null");
Assert.notNull(predicate, "Predicate must not be null");
for (T item : source) {
if (predicate.test(item)) {
return PRESENT;
}
}
return ABSENT;
}
}

@ -16,7 +16,6 @@
package org.springframework.boot.context.properties.source;
import java.util.Optional;
import java.util.function.Predicate;
import org.springframework.util.Assert;
@ -49,10 +48,13 @@ class FilteredConfigurationPropertiesSource implements ConfigurationPropertySour
}
@Override
public Optional<Boolean> containsDescendantOf(ConfigurationPropertyName name) {
public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
ConfigurationPropertyState result = this.source.containsDescendantOf(name);
if (result == ConfigurationPropertyState.PRESENT) {
// We can't be sure a contained descendant won't be filtered
return this.source.containsDescendantOf(name)
.flatMap((result) -> result ? Optional.empty() : Optional.of(result));
return ConfigurationPropertyState.UNKNOWN;
}
return result;
}
protected ConfigurationPropertySource getSource() {

@ -16,7 +16,6 @@
package org.springframework.boot.context.properties.source;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
@ -48,8 +47,8 @@ class FilteredIterableConfigurationPropertiesSource
}
@Override
public Optional<Boolean> containsDescendantOf(ConfigurationPropertyName name) {
return Optional.of(stream().anyMatch(name::isAncestorOf));
public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
return ConfigurationPropertyState.search(this, name::isAncestorOf);
}
}

@ -17,7 +17,6 @@
package org.springframework.boot.context.properties.source;
import java.util.Iterator;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;
@ -62,8 +61,8 @@ public interface IterableConfigurationPropertySource
Stream<ConfigurationPropertyName> stream();
@Override
default Optional<Boolean> containsDescendantOf(ConfigurationPropertyName name) {
return Optional.of(stream().anyMatch(name::isAncestorOf));
default ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
return ConfigurationPropertyState.search(this, name::isAncestorOf);
}
@Override

@ -20,7 +20,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import org.springframework.boot.env.RandomValuePropertySource;
@ -62,7 +61,7 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource {
private final PropertyMapper mapper;
private final Function<ConfigurationPropertyName, Optional<Boolean>> containsDescendantOfMethod;
private final Function<ConfigurationPropertyName, ConfigurationPropertyState> containsDescendantOfMethod;
/**
* Create a new {@link SpringConfigurationPropertySource} implementation.
@ -73,13 +72,13 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource {
*/
SpringConfigurationPropertySource(PropertySource<?> propertySource,
PropertyMapper mapper,
Function<ConfigurationPropertyName, Optional<Boolean>> containsDescendantOfMethod) {
Function<ConfigurationPropertyName, ConfigurationPropertyState> containsDescendantOfMethod) {
Assert.notNull(propertySource, "PropertySource must not be null");
Assert.notNull(mapper, "Mapper must not be null");
this.propertySource = propertySource;
this.mapper = new ExceptionSwallowingPropertyMapper(mapper);
this.containsDescendantOfMethod = (containsDescendantOfMethod != null
? containsDescendantOfMethod : (n) -> Optional.empty());
? containsDescendantOfMethod : (n) -> ConfigurationPropertyState.UNKNOWN);
}
@Override
@ -90,7 +89,7 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource {
}
@Override
public Optional<Boolean> containsDescendantOf(ConfigurationPropertyName name) {
public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
return this.containsDescendantOfMethod.apply(name);
}
@ -173,11 +172,11 @@ class SpringConfigurationPropertySource implements ConfigurationPropertySource {
return source;
}
private static Function<ConfigurationPropertyName, Optional<Boolean>> getContainsDescendantOfMethod(
private static Function<ConfigurationPropertyName, ConfigurationPropertyState> getContainsDescendantOfMethod(
PropertySource<?> source) {
if (source instanceof RandomValuePropertySource) {
return (name) -> Optional
.of(name.isAncestorOf(RANDOM) || name.equals(RANDOM));
return (name) -> (name.isAncestorOf(RANDOM) || name.equals(RANDOM)
? ConfigurationPropertyState.PRESENT : ConfigurationPropertyState.ABSENT);
}
return null;
}

@ -20,7 +20,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.core.env.EnumerablePropertySource;
@ -88,8 +87,8 @@ class SpringIterableConfigurationPropertySource extends SpringConfigurationPrope
}
@Override
public Optional<Boolean> containsDescendantOf(ConfigurationPropertyName name) {
return Optional.of(stream().anyMatch(name::isAncestorOf));
public ConfigurationPropertyState containsDescendantOf(ConfigurationPropertyName name) {
return ConfigurationPropertyState.search(this, name::isAncestorOf);
}
private List<ConfigurationPropertyName> getConfigurationPropertyNames() {

@ -16,8 +16,6 @@
package org.springframework.boot.context.properties.source;
import java.util.Optional;
import org.junit.Test;
import org.mockito.Answers;
@ -57,56 +55,65 @@ public class AliasedConfigurationPropertySourceTests {
}
@Test
public void containsDescendantOfWhenSourceReturnsEmptyShouldReturnEmpty()
public void containsDescendantOfWhenSourceReturnsUnknownShouldReturnUnknown()
throws Exception {
ConfigurationPropertyName name = ConfigurationPropertyName.of("foo");
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class,
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
given(source.containsDescendantOf(name)).willReturn(Optional.empty());
given(source.containsDescendantOf(name))
.willReturn(ConfigurationPropertyState.UNKNOWN);
ConfigurationPropertySource aliased = source
.withAliases(new ConfigurationPropertyNameAliases("foo.bar", "foo.bar1"));
assertThat(aliased.containsDescendantOf(name)).isEmpty();
assertThat(aliased.containsDescendantOf(name))
.isEqualTo(ConfigurationPropertyState.UNKNOWN);
}
@Test
public void containsDescendantOfWhenAliasReturnsEmptyShouldReturnEmpty()
public void containsDescendantOfWhenSourceReturnsPresentShouldReturnPresent()
throws Exception {
ConfigurationPropertyName name = ConfigurationPropertyName.of("foo");
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class,
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
given(source.containsDescendantOf(name)).willReturn(Optional.of(true));
given(source.containsDescendantOf(name))
.willReturn(ConfigurationPropertyState.PRESENT);
given(source.containsDescendantOf(ConfigurationPropertyName.of("bar")))
.willReturn(Optional.empty());
.willReturn(ConfigurationPropertyState.UNKNOWN);
ConfigurationPropertySource aliased = source
.withAliases(new ConfigurationPropertyNameAliases("foo", "bar"));
assertThat(aliased.containsDescendantOf(name)).isEmpty();
.withAliases(new ConfigurationPropertyNameAliases("foo.bar", "foo.bar1"));
assertThat(aliased.containsDescendantOf(name))
.isEqualTo(ConfigurationPropertyState.PRESENT);
}
@Test
public void containsDescendantOfWhenAllAreFalseShouldReturnFalse() throws Exception {
public void containsDescendantOfWhenAllAreAbsentShouldReturnAbsent()
throws Exception {
ConfigurationPropertyName name = ConfigurationPropertyName.of("foo");
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class,
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
given(source.containsDescendantOf(name)).willReturn(Optional.of(false));
given(source.containsDescendantOf(name))
.willReturn(ConfigurationPropertyState.ABSENT);
given(source.containsDescendantOf(ConfigurationPropertyName.of("bar")))
.willReturn(Optional.of(false));
.willReturn(ConfigurationPropertyState.ABSENT);
ConfigurationPropertySource aliased = source
.withAliases(new ConfigurationPropertyNameAliases("foo", "bar"));
assertThat(aliased.containsDescendantOf(name)).contains(false);
assertThat(aliased.containsDescendantOf(name))
.isEqualTo(ConfigurationPropertyState.ABSENT);
}
@Test
public void containsDescendantOfWhenAnyIsTrueShouldReturnTrue() throws Exception {
public void containsDescendantOfWhenAnyIsPresentShouldReturnPresent()
throws Exception {
ConfigurationPropertyName name = ConfigurationPropertyName.of("foo");
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class,
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
given(source.containsDescendantOf(name)).willReturn(Optional.of(false));
given(source.containsDescendantOf(name))
.willReturn(ConfigurationPropertyState.ABSENT);
given(source.containsDescendantOf(ConfigurationPropertyName.of("bar")))
.willReturn(Optional.of(true));
.willReturn(ConfigurationPropertyState.PRESENT);
ConfigurationPropertySource aliased = source
.withAliases(new ConfigurationPropertyNameAliases("foo", "bar"));
assertThat(aliased.containsDescendantOf(name)).contains(true);
assertThat(aliased.containsDescendantOf(name))
.isEqualTo(ConfigurationPropertyState.PRESENT);
}
private Object getValue(ConfigurationPropertySource source, String name) {

@ -0,0 +1,69 @@
/*
* Copyright 2012-2017 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.context.properties.source;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ConfigurationPropertyState}.
*
* @author Phillip Webb
*/
public class ConfigurationPropertyStateTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void searchWhenIterableIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Source must not be null");
ConfigurationPropertyState.search(null, (e) -> true);
}
@Test
public void searchWhenPredicateIsNullShouldThrowException() throws Exception {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Predicate must not be null");
ConfigurationPropertyState.search(Collections.emptyList(), null);
}
@Test
public void searchWhenContainsItemShouldReturnPresent() {
List<String> source = Arrays.asList("a", "b", "c");
ConfigurationPropertyState result = ConfigurationPropertyState.search(source,
"b"::equals);
assertThat(result).isEqualTo(ConfigurationPropertyState.PRESENT);
}
@Test
public void searchWhenContainsNoItemShouldReturnAbsent() {
List<String> source = Arrays.asList("a", "x", "c");
ConfigurationPropertyState result = ConfigurationPropertyState.search(source,
"b"::equals);
assertThat(result).isEqualTo(ConfigurationPropertyState.ABSENT);
}
}

@ -17,7 +17,6 @@
package org.springframework.boot.context.properties.source;
import java.util.Objects;
import java.util.Optional;
import org.junit.Rule;
import org.junit.Test;
@ -74,9 +73,11 @@ public class FilteredConfigurationPropertiesSourceTests {
ConfigurationPropertyName name = ConfigurationPropertyName.of("foo");
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class,
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
given(source.containsDescendantOf(name)).willReturn(Optional.empty());
given(source.containsDescendantOf(name))
.willReturn(ConfigurationPropertyState.UNKNOWN);
ConfigurationPropertySource filtered = source.filter((n) -> true);
assertThat(filtered.containsDescendantOf(name)).isEmpty();
assertThat(filtered.containsDescendantOf(name))
.isEqualTo(ConfigurationPropertyState.UNKNOWN);
}
@Test
@ -85,9 +86,11 @@ public class FilteredConfigurationPropertiesSourceTests {
ConfigurationPropertyName name = ConfigurationPropertyName.of("foo");
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class,
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
given(source.containsDescendantOf(name)).willReturn(Optional.of(false));
given(source.containsDescendantOf(name))
.willReturn(ConfigurationPropertyState.ABSENT);
ConfigurationPropertySource filtered = source.filter((n) -> true);
assertThat(filtered.containsDescendantOf(name)).contains(false);
assertThat(filtered.containsDescendantOf(name))
.isEqualTo(ConfigurationPropertyState.ABSENT);
}
@Test
@ -96,9 +99,11 @@ public class FilteredConfigurationPropertiesSourceTests {
ConfigurationPropertyName name = ConfigurationPropertyName.of("foo");
ConfigurationPropertySource source = mock(ConfigurationPropertySource.class,
withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS));
given(source.containsDescendantOf(name)).willReturn(Optional.of(true));
given(source.containsDescendantOf(name))
.willReturn(ConfigurationPropertyState.PRESENT);
ConfigurationPropertySource filtered = source.filter((n) -> true);
assertThat(filtered.containsDescendantOf(name)).isEmpty();
assertThat(filtered.containsDescendantOf(name))
.isEqualTo(ConfigurationPropertyState.UNKNOWN);
}
protected final ConfigurationPropertySource createTestSource() {

@ -51,9 +51,9 @@ public class FilteredIterableConfigurationPropertiesSourceTests
source.put("faf.bar[0]", "1");
IterableConfigurationPropertySource filtered = source.filter(this::noBrackets);
assertThat(filtered.containsDescendantOf(ConfigurationPropertyName.of("foo")))
.contains(true);
.isEqualTo(ConfigurationPropertyState.PRESENT);
assertThat(filtered.containsDescendantOf(ConfigurationPropertyName.of("faf")))
.contains(false);
.isEqualTo(ConfigurationPropertyState.ABSENT);
}
private boolean noBrackets(ConfigurationPropertyName name) {

@ -19,7 +19,6 @@ package org.springframework.boot.context.properties.source;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.boot.origin.MockOrigin;
@ -99,8 +98,9 @@ public class MockConfigurationPropertySource
}
@Override
public Optional<Boolean> containsDescendantOf(ConfigurationPropertyName name) {
return Optional.empty();
public ConfigurationPropertyState containsDescendantOf(
ConfigurationPropertyName name) {
return ConfigurationPropertyState.UNKNOWN;
}
}

@ -122,7 +122,7 @@ public class SpringConfigurationPropertySourceTests {
SpringConfigurationPropertySource adapter = new SpringConfigurationPropertySource(
propertySource, DefaultPropertyMapper.INSTANCE, null);
assertThat(adapter.containsDescendantOf(ConfigurationPropertyName.of("foo")))
.isEmpty();
.isEqualTo(ConfigurationPropertyState.UNKNOWN);
}
@Test

@ -165,11 +165,11 @@ public class SpringIterableConfigurationPropertySourceTests {
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
propertySource, DefaultPropertyMapper.INSTANCE);
assertThat(adapter.containsDescendantOf(ConfigurationPropertyName.of("foo")))
.contains(true);
.isEqualTo(ConfigurationPropertyState.PRESENT);
assertThat(adapter.containsDescendantOf(ConfigurationPropertyName.of("faf")))
.contains(false);
.isEqualTo(ConfigurationPropertyState.ABSENT);
assertThat(adapter.containsDescendantOf(ConfigurationPropertyName.of("fof")))
.contains(false);
.isEqualTo(ConfigurationPropertyState.ABSENT);
}
/**

Loading…
Cancel
Save