Skip to content

Commit ffb8ba9

Browse files
committed
Remove COW list based on feedback
1 parent 9e93aa2 commit ffb8ba9

2 files changed

Lines changed: 64 additions & 49 deletions

File tree

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package io.jooby.spi;
22

3+
import java.util.ArrayList;
4+
import java.util.List;
35
import java.util.ServiceLoader;
4-
import java.util.concurrent.CopyOnWriteArrayList;
56

67
import javax.annotation.Nullable;
78

@@ -19,21 +20,62 @@
1920
public final class ValueConverters {
2021

2122
// Allow thread safe adding of ValueConverters.
22-
private final CopyOnWriteArrayList<ValueConverter> valueConverters;
23+
private final Iterable<ValueConverter> valueConverters;
2324

2425
// Initialization on demand
2526
private static final class Hidden {
2627

27-
private static final ValueConverters INSTANCE = ValueConverters.create().fromServiceLoader();
28+
private static volatile ValueConverters instance = ValueConverters.builder().fromServiceLoader().build();
2829
}
2930

30-
private ValueConverters(CopyOnWriteArrayList<ValueConverter> valueConverters) {
31+
private ValueConverters(Iterable<ValueConverter> valueConverters) {
3132
super();
3233
this.valueConverters = valueConverters;
3334
}
3435

35-
static ValueConverters create() {
36-
return new ValueConverters(new CopyOnWriteArrayList<>());
36+
static Builder builder() {
37+
return new Builder();
38+
}
39+
40+
static final class Builder {
41+
42+
private final List<ValueConverter> valueConverters = new ArrayList<>();
43+
44+
Builder fromServiceLoader() {
45+
ServiceLoader<ValueConverter> sl = ServiceLoader.load(ValueConverter.class);
46+
// If any failes to load we will fail entirely.
47+
// The value converters found earlier in the classpath take precedence.
48+
sl.forEach(this::add);
49+
return this;
50+
}
51+
52+
/**
53+
* You can add value converters programmatic. For now its protected. Its
54+
* also to aid unit testing since serviceloader is inherently static
55+
* singleton.
56+
*
57+
* @param vc
58+
* @return
59+
*/
60+
Builder add(ValueConverter vc) {
61+
valueConverters.add(vc);
62+
return this;
63+
}
64+
65+
Builder clear() {
66+
valueConverters.clear();
67+
return this;
68+
}
69+
70+
ValueConverters build() {
71+
return new ValueConverters(valueConverters);
72+
}
73+
74+
ValueConverters set() {
75+
ValueConverters vc = build();
76+
Hidden.instance = vc;
77+
return vc;
78+
}
3779
}
3880

3981
/**
@@ -44,7 +86,8 @@ static ValueConverters create() {
4486
* @param c
4587
* desired type
4688
* @return the type if converted or null if conversion was not possible.
47-
* @throws TypeMismatchException failure in a converter
89+
* @throws TypeMismatchException
90+
* failure in a converter
4891
*/
4992
public @Nullable Object convert(ValueContainer v, Class<?> c) throws TypeMismatchException {
5093
Object result = null;
@@ -59,37 +102,13 @@ static ValueConverters create() {
59102
return result;
60103
}
61104

62-
ValueConverters fromServiceLoader() {
63-
ServiceLoader<ValueConverter> sl = ServiceLoader.load(ValueConverter.class);
64-
// If any failes to load we will fail entirely.
65-
// The value converters found earlier in the classpath take precedence.
66-
sl.forEach(this::add);
67-
return this;
68-
}
69-
70-
/**
71-
* You can add value converters programmatic. For now its protected. Its also
72-
* to aid unit testing since serviceloader is inherently static singleton.
73-
*
74-
* @param vc
75-
* @return
76-
*/
77-
/* private */ ValueConverters add(ValueConverter vc) {
78-
valueConverters.add(vc);
79-
return this;
80-
}
81-
82-
ValueConverters clear() {
83-
valueConverters.clear();
84-
return this;
85-
}
86-
87105
/**
88106
* The ValueConverters singleton usually preloaded by the ServiceLoader.
107+
*
89108
* @return the shared singleton used by Jooby
90109
*/
91110
public static ValueConverters getInstance() {
92-
return ValueConverters.Hidden.INSTANCE;
111+
return ValueConverters.Hidden.instance;
93112
}
94113

95114
}

jooby/src/test/java/io/jooby/spi/ValueConvertersTest.java

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,29 @@
1111
import io.jooby.QueryString;
1212
import io.jooby.internal.UrlParser;
1313

14-
1514
class ValueConvertersTest {
1615

1716
@AfterAll
1817
static void restoreFromServiceLoader() {
19-
//Restore ValueConvert list for other unit tests.
20-
ValueConverters.getInstance().clear().fromServiceLoader();
18+
// Restore ValueConvert list for other unit tests.
19+
ValueConverters.builder().fromServiceLoader().set();
2120
}
22-
21+
2322
@Test
2423
void testConvert() {
25-
ValueConverters.getInstance()
26-
.clear()
27-
.add((value, type) -> {
24+
ValueConverters.builder().add((value, type) -> {
2825
if (type == MyValue.class) {
2926
MyValue mv = new MyValue();
30-
// we have chosen simple parameters names to make sure we don't get a false positve
27+
// we have chosen simple parameters names to make sure we don't get a
28+
// false positve
3129
// from the reflection converter.
3230
mv.name = value.get("n").value();
33-
//TODO: ValueContainer probably should have primitive convert methods.
31+
// TODO: ValueContainer probably should have primitive convert methods.
3432
mv.order = Integer.parseInt(value.get("o").value());
3533
return mv;
3634
}
3735
return null;
38-
});
36+
}).set();
3937
queryString("n=stuff&o=1", queryString -> {
4038
MyValue mv = queryString.to(MyValue.class);
4139
assertEquals("stuff", mv.name);
@@ -45,20 +43,18 @@ void testConvert() {
4543
try {
4644
queryString.to(MyValue.class);
4745
fail();
48-
}
49-
catch (MissingValueException mve) {
46+
} catch (MissingValueException mve) {
5047
assertEquals("Missing value: 'o'", mve.getMessage());
5148
}
5249
});
5350
}
54-
51+
5552
static class MyValue {
53+
5654
public String name;
5755
public int order;
5856
}
59-
60-
61-
57+
6258
private void queryString(String queryString, Consumer<QueryString> consumer) {
6359
consumer.accept(UrlParser.queryString(queryString));
6460
}

0 commit comments

Comments
 (0)