1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.any23.configuration;
19
20 import java.lang.reflect.GenericArrayType;
21 import java.lang.reflect.ParameterizedType;
22 import java.lang.reflect.Type;
23 import java.lang.reflect.TypeVariable;
24 import java.util.HashMap;
25 import java.util.Objects;
26 import java.util.Optional;
27 import java.util.regex.Pattern;
28
29
30
31
32
33
34 public abstract class Setting<V> implements Cloneable {
35
36 private final Key key;
37 private V value;
38
39
40
41
42
43
44
45
46
47
48
49
50
51 protected Setting(String identifier, V defaultValue) {
52 checkIdentifier(identifier);
53 this.key = new Key(identifier, lookupValueType(getClass(), identifier), defaultValue != null);
54 this.value = defaultValue;
55 }
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 protected Setting(String identifier, Class<V> valueType, V defaultValue) {
71 this(identifier, defaultValue, valueType);
72 if (valueType.isArray()) {
73 throw new IllegalArgumentException(identifier + " value class must be immutable");
74 } else if (valueType.getTypeParameters().length != 0) {
75 throw new IllegalArgumentException(
76 identifier + " setting key must fill in type parameters for " + valueType.toGenericString());
77 } else if (valueType.isPrimitive()) {
78
79
80 throw new IllegalArgumentException(identifier + " value class cannot be primitive");
81 }
82 }
83
84 private Setting(String identifier, V defaultValue, Class<V> valueType) {
85 checkIdentifier(identifier);
86 this.key = new Key(identifier, valueType, defaultValue != null);
87 this.value = defaultValue;
88 }
89
90
91
92
93 public final String getIdentifier() {
94 return key.identifier;
95 }
96
97
98
99
100
101
102
103
104
105
106
107
108 protected void checkValue(V newValue) throws Exception {
109 if (newValue == null && key.nonnull) {
110 throw new NullPointerException();
111 }
112 }
113
114
115
116
117 public final V getValue() {
118 return value;
119 }
120
121
122
123
124 public final Type getValueType() {
125 return key.valueType;
126 }
127
128
129
130
131
132
133
134
135
136 @SuppressWarnings("unchecked")
137 public final <S extends Setting<?>> Optional<S> as(S setting) {
138 return key == ((Setting<?>) setting).key ? Optional.of((S) this) : Optional.empty();
139 }
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156 public final Setting<V> withValue(V newValue) {
157 return clone(this, newValue);
158 }
159
160 @Override
161 protected final Object clone() {
162 try {
163
164 return super.clone();
165 } catch (CloneNotSupportedException e) {
166 throw new AssertionError(e);
167 }
168 }
169
170
171
172
173
174 @Override
175 public final boolean equals(Object o) {
176 if (this == o)
177 return true;
178 if (!(o instanceof Setting))
179 return false;
180
181 Setting<?> setting = (Setting<?>) o;
182 return key == setting.key && Objects.equals(value, setting.value);
183 }
184
185 @Override
186 public final int hashCode() {
187 return key.hashCode() ^ Objects.hashCode(value);
188 }
189
190 @Override
191 public String toString() {
192 return key.identifier + "=" + value;
193 }
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 public static Setting<Boolean> create(String identifier, Boolean defaultValue) {
209 return new Impl<>(identifier, defaultValue, Boolean.class);
210 }
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225 public static Setting<String> create(String identifier, String defaultValue) {
226 return new Impl<>(identifier, defaultValue, String.class);
227 }
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242 public static Setting<Integer> create(String identifier, Integer defaultValue) {
243 return new Impl<>(identifier, defaultValue, Integer.class);
244 }
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259 public static Setting<Long> create(String identifier, Long defaultValue) {
260 return new Impl<>(identifier, defaultValue, Long.class);
261 }
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276 public static Setting<Float> create(String identifier, Float defaultValue) {
277 return new Impl<>(identifier, defaultValue, Float.class);
278 }
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293 public static Setting<Double> create(String identifier, Double defaultValue) {
294 return new Impl<>(identifier, defaultValue, Double.class);
295 }
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314 public static <V> Setting<V> create(String identifier, Class<V> valueType, V defaultValue) {
315 return new Impl<>(identifier, valueType, defaultValue);
316 }
317
318
319
320
321
322
323
324 private static class Impl<V> extends Setting<V> {
325
326 private Impl(String identifier, V defaultValue, Class<V> valueType) {
327 super(identifier, defaultValue, valueType);
328 }
329
330
331 private Impl(String identifier, Class<V> valueType, V defaultValue) {
332 super(identifier, valueType, defaultValue);
333 }
334 }
335
336 private static final class Key {
337 final String identifier;
338 final Type valueType;
339 final boolean nonnull;
340
341 Key(String identifier, Type valueType, boolean nonnull) {
342 this.identifier = identifier;
343 this.valueType = valueType;
344 this.nonnull = nonnull;
345 }
346 }
347
348 @SuppressWarnings("unchecked")
349 private static <V, S extends Setting<V>> S clone(S setting, V newValue) {
350 try {
351 setting.checkValue(newValue);
352 } catch (Exception e) {
353 throw new IllegalArgumentException("invalid value for key '" + ((Setting<V>) setting).key.identifier + "': "
354 + ((Setting<V>) setting).value, e);
355 }
356
357
358 S s = (S) setting.clone();
359
360 assert ((Setting<V>) s).key == ((Setting<V>) setting).key;
361 assert ((Setting<V>) s).getClass().equals(setting.getClass());
362
363 ((Setting<V>) s).value = newValue;
364 return s;
365 }
366
367 private static final Pattern identifierPattern = Pattern.compile("[a-z][0-9a-z]*(\\.[a-z][0-9a-z]*)*");
368
369 private static void checkIdentifier(String identifier) {
370 if (identifier == null) {
371 throw new IllegalArgumentException("identifier cannot be null");
372 }
373 if (!identifierPattern.matcher(identifier).matches()) {
374 throw new IllegalArgumentException("identifier does not match " + identifierPattern.pattern());
375 }
376 }
377
378 private static Type lookupValueType(Class<?> rawType, String identifier) {
379 HashMap<TypeVariable<?>, Type> mapping = new HashMap<>();
380 assert rawType != Setting.class;
381 for (;;) {
382 Type superclass = rawType.getGenericSuperclass();
383 if (superclass instanceof ParameterizedType) {
384 rawType = (Class<?>) ((ParameterizedType) superclass).getRawType();
385 Type[] args = ((ParameterizedType) superclass).getActualTypeArguments();
386 if (Setting.class.equals(rawType)) {
387 Type type = args[0];
388 type = mapping.getOrDefault(type, type);
389 if (type instanceof Class) {
390 if (((Class<?>) type).isArray()) {
391 throw new IllegalArgumentException(identifier + " value class must be immutable");
392 } else if (((Class<?>) type).getTypeParameters().length != 0) {
393 throw new IllegalArgumentException(identifier + " setting must fill in type parameters for "
394 + ((Class<?>) type).toGenericString());
395 }
396 } else if (type instanceof GenericArrayType) {
397 throw new IllegalArgumentException(identifier + " value class must be immutable");
398 } else if (type instanceof TypeVariable) {
399 throw new IllegalArgumentException(
400 "Invalid setting type 'Key<" + type.getTypeName() + ">' for identifier " + identifier);
401 } else if (!(type instanceof ParameterizedType)) {
402 throw new IllegalArgumentException(
403 identifier + " invalid type " + type + " (" + type.getClass().getName() + ")");
404 }
405 return type;
406 }
407 TypeVariable<?>[] vars = rawType.getTypeParameters();
408 for (int i = 0, len = vars.length; i < len; i++) {
409 Type t = args[i];
410 mapping.put(vars[i], t instanceof TypeVariable ? mapping.get(t) : t);
411 }
412 } else {
413 rawType = (Class<?>) superclass;
414 if (Setting.class.equals(rawType)) {
415 throw new IllegalArgumentException(rawType + " does not supply type arguments");
416 }
417 }
418 }
419 }
420
421 }