Skip to content

Commit 1292947

Browse files
committed
Introduce computeAttribute() in AttributeAccessor
This commit introduces computeAttribute() as an interface default method in the AttributeAccessor API. This serves as a convenience analogous to the computeIfAbsent() method in java.util.Map. Closes gh-26281
1 parent ef6a582 commit 1292947

File tree

4 files changed

+100
-17
lines changed

4 files changed

+100
-17
lines changed

spring-core/src/main/java/org/springframework/core/AttributeAccessor.java

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,20 +16,24 @@
1616

1717
package org.springframework.core;
1818

19+
import java.util.function.Function;
20+
1921
import org.springframework.lang.Nullable;
22+
import org.springframework.util.Assert;
2023

2124
/**
2225
* Interface defining a generic contract for attaching and accessing metadata
2326
* to/from arbitrary objects.
2427
*
2528
* @author Rob Harrop
29+
* @author Sam Brannen
2630
* @since 2.0
2731
*/
2832
public interface AttributeAccessor {
2933

3034
/**
3135
* Set the attribute defined by {@code name} to the supplied {@code value}.
32-
* If {@code value} is {@code null}, the attribute is {@link #removeAttribute removed}.
36+
* <p>If {@code value} is {@code null}, the attribute is {@link #removeAttribute removed}.
3337
* <p>In general, users should take care to prevent overlaps with other
3438
* metadata attributes by using fully-qualified names, perhaps using
3539
* class or package names as prefix.
@@ -40,16 +44,48 @@ public interface AttributeAccessor {
4044

4145
/**
4246
* Get the value of the attribute identified by {@code name}.
43-
* Return {@code null} if the attribute doesn't exist.
47+
* <p>Return {@code null} if the attribute doesn't exist.
4448
* @param name the unique attribute key
4549
* @return the current value of the attribute, if any
4650
*/
4751
@Nullable
4852
Object getAttribute(String name);
4953

54+
/**
55+
* Compute a new value for the attribute identified by {@code name} if
56+
* necessary and {@linkplain #setAttribute set} the new value in this
57+
* {@code AttributeAccessor}.
58+
* <p>If a value for the attribute identified by {@code name} already exists
59+
* in this {@code AttributeAccessor}, the existing value will be returned
60+
* without applying the supplied compute function.
61+
* <p>The default implementation of this method is not thread safe but can
62+
* overridden by concrete implementations of this interface.
63+
* @param <T> the type of the attribute value
64+
* @param name the unique attribute key
65+
* @param computeFunction a function that computes a new value for the attribute
66+
* name; the function must not return a {@code null} value
67+
* @return the existing value or newly computed value for the named attribute
68+
* @see #getAttribute(String)
69+
* @see #setAttribute(String, Object)
70+
* @since 5.3.3
71+
*/
72+
@SuppressWarnings("unchecked")
73+
default <T> T computeAttribute(String name, Function<String, T> computeFunction) {
74+
Assert.notNull(name, "Name must not be null");
75+
Assert.notNull(computeFunction, "Compute function must not be null");
76+
Object value = getAttribute(name);
77+
if (value == null) {
78+
value = computeFunction.apply(name);
79+
Assert.state(value != null,
80+
() -> String.format("Compute function must not return null for attribute named '%s'", name));
81+
setAttribute(name, value);
82+
}
83+
return (T) value;
84+
}
85+
5086
/**
5187
* Remove the attribute identified by {@code name} and return its value.
52-
* Return {@code null} if no attribute under {@code name} is found.
88+
* <p>Return {@code null} if no attribute under {@code name} is found.
5389
* @param name the unique attribute key
5490
* @return the last value of the attribute, if any
5591
*/
@@ -58,7 +94,7 @@ public interface AttributeAccessor {
5894

5995
/**
6096
* Return {@code true} if the attribute identified by {@code name} exists.
61-
* Otherwise return {@code false}.
97+
* <p>Otherwise return {@code false}.
6298
* @param name the unique attribute key
6399
*/
64100
boolean hasAttribute(String name);

spring-core/src/main/java/org/springframework/core/AttributeAccessorSupport.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.io.Serializable;
2020
import java.util.LinkedHashMap;
2121
import java.util.Map;
22+
import java.util.function.Function;
2223

2324
import org.springframework.lang.Nullable;
2425
import org.springframework.util.Assert;
@@ -32,6 +33,7 @@
3233
*
3334
* @author Rob Harrop
3435
* @author Juergen Hoeller
36+
* @author Sam Brannen
3537
* @since 2.0
3638
*/
3739
@SuppressWarnings("serial")
@@ -59,6 +61,17 @@ public Object getAttribute(String name) {
5961
return this.attributes.get(name);
6062
}
6163

64+
@Override
65+
@SuppressWarnings("unchecked")
66+
public <T> T computeAttribute(String name, Function<String, T> computeFunction) {
67+
Assert.notNull(name, "Name must not be null");
68+
Assert.notNull(computeFunction, "Compute function must not be null");
69+
Object value = this.attributes.computeIfAbsent(name, computeFunction);
70+
Assert.state(value != null,
71+
() -> String.format("Compute function must not return null for attribute named '%s'", name));
72+
return (T) value;
73+
}
74+
6275
@Override
6376
@Nullable
6477
public Object removeAttribute(String name) {

spring-core/src/test/java/org/springframework/core/AttributeAccessorSupportTests.java

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,53 +17,75 @@
1717
package org.springframework.core;
1818

1919
import java.util.Arrays;
20+
import java.util.concurrent.atomic.AtomicInteger;
21+
import java.util.function.Function;
2022

2123
import org.junit.jupiter.api.Test;
2224

2325
import static org.assertj.core.api.Assertions.assertThat;
2426

2527
/**
28+
* Unit tests for {@link AttributeAccessorSupport}.
29+
*
2630
* @author Rob Harrop
2731
* @author Sam Brannen
2832
* @since 2.0
2933
*/
3034
class AttributeAccessorSupportTests {
3135

32-
private static final String NAME = "foo";
36+
private static final String NAME = "name";
37+
38+
private static final String VALUE = "value";
3339

34-
private static final String VALUE = "bar";
40+
private final AttributeAccessor attributeAccessor = new SimpleAttributeAccessorSupport();
3541

36-
private AttributeAccessor attributeAccessor = new SimpleAttributeAccessorSupport();
3742

3843
@Test
39-
void setAndGet() throws Exception {
44+
void setAndGet() {
4045
this.attributeAccessor.setAttribute(NAME, VALUE);
4146
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo(VALUE);
4247
}
4348

4449
@Test
45-
void setAndHas() throws Exception {
50+
void setAndHas() {
4651
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
4752
this.attributeAccessor.setAttribute(NAME, VALUE);
4853
assertThat(this.attributeAccessor.hasAttribute(NAME)).isTrue();
4954
}
5055

5156
@Test
52-
void remove() throws Exception {
57+
void computeAttribute() {
58+
AtomicInteger atomicInteger = new AtomicInteger();
59+
Function<String, String> computeFunction = name -> "computed-" + atomicInteger.incrementAndGet();
60+
61+
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
62+
this.attributeAccessor.computeAttribute(NAME, computeFunction);
63+
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo("computed-1");
64+
this.attributeAccessor.computeAttribute(NAME, computeFunction);
65+
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo("computed-1");
66+
67+
this.attributeAccessor.removeAttribute(NAME);
68+
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
69+
this.attributeAccessor.computeAttribute(NAME, computeFunction);
70+
assertThat(this.attributeAccessor.getAttribute(NAME)).isEqualTo("computed-2");
71+
}
72+
73+
@Test
74+
void remove() {
5375
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
5476
this.attributeAccessor.setAttribute(NAME, VALUE);
5577
assertThat(this.attributeAccessor.removeAttribute(NAME)).isEqualTo(VALUE);
5678
assertThat(this.attributeAccessor.hasAttribute(NAME)).isFalse();
5779
}
5880

5981
@Test
60-
void attributeNames() throws Exception {
82+
void attributeNames() {
6183
this.attributeAccessor.setAttribute(NAME, VALUE);
6284
this.attributeAccessor.setAttribute("abc", "123");
6385
String[] attributeNames = this.attributeAccessor.attributeNames();
6486
Arrays.sort(attributeNames);
65-
assertThat(Arrays.binarySearch(attributeNames, NAME) > -1).isTrue();
66-
assertThat(Arrays.binarySearch(attributeNames, "abc") > -1).isTrue();
87+
assertThat(Arrays.binarySearch(attributeNames, "abc")).isEqualTo(0);
88+
assertThat(Arrays.binarySearch(attributeNames, NAME)).isEqualTo(1);
6789
}
6890

6991
@SuppressWarnings("serial")

spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContext.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.lang.reflect.Method;
2020
import java.util.Map;
2121
import java.util.concurrent.ConcurrentHashMap;
22+
import java.util.function.Function;
2223

2324
import org.springframework.context.ApplicationContext;
2425
import org.springframework.context.ConfigurableApplicationContext;
@@ -200,6 +201,17 @@ public Object getAttribute(String name) {
200201
return this.attributes.get(name);
201202
}
202203

204+
@Override
205+
@SuppressWarnings("unchecked")
206+
public <T> T computeAttribute(String name, Function<String, T> computeFunction) {
207+
Assert.notNull(name, "Name must not be null");
208+
Assert.notNull(computeFunction, "Compute function must not be null");
209+
Object value = this.attributes.computeIfAbsent(name, computeFunction);
210+
Assert.state(value != null,
211+
() -> String.format("Compute function must not return null for attribute named '%s'", name));
212+
return (T) value;
213+
}
214+
203215
@Override
204216
@Nullable
205217
public Object removeAttribute(String name) {

0 commit comments

Comments
 (0)