1 | package com.hammurapi.common.concurrent; |
2 | |
3 | import java.util.Arrays; |
4 | import java.util.HashSet; |
5 | import java.util.Map; |
6 | import java.util.Set; |
7 | import java.util.concurrent.Callable; |
8 | import java.util.concurrent.ExecutorService; |
9 | import java.util.concurrent.Future; |
10 | import java.util.concurrent.ScheduledExecutorService; |
11 | import java.util.concurrent.TimeUnit; |
12 | import java.util.concurrent.locks.ReadWriteLock; |
13 | |
14 | import com.hammurapi.common.Context; |
15 | import com.hammurapi.convert.Converter; |
16 | import com.hammurapi.convert.ConvertingService; |
17 | |
18 | /** |
19 | * |
20 | * @author Pavel Vlasov |
21 | * |
22 | * @param <KP> Key path |
23 | * @param <KE> Key path element |
24 | */ |
25 | public abstract class AbstractPropertySet<KP,KE> implements PropertySet<KP>, ReadWriteLock { |
26 | |
27 | protected Converter converter; |
28 | protected ExecutorService executorService; |
29 | protected PropertySet<KP>[] shadows; |
30 | // protected ClassLoader classLoader; |
31 | protected Context context; |
32 | |
33 | private PropertySet<KP> shadowPropertySet = new PropertySet<KP>() { |
34 | |
35 | @Override |
36 | public <T> T getAdapter(Class<T> type, Context context) { |
37 | throw new UnsupportedOperationException(); // Maybe implement. |
38 | } |
39 | |
40 | @Override |
41 | public Set<KP> keySet() { |
42 | HashSet<KP> ret = new HashSet<KP>(); |
43 | |
44 | for (PropertySet<KP> ch: shadows) { |
45 | if (ch!=null) { |
46 | ret.addAll(ch.keySet()); |
47 | } |
48 | } |
49 | return ret; |
50 | } |
51 | |
52 | @Override |
53 | public PropertySet<KP> subset(KP prefix) { |
54 | return new AbstractSubSet<KP>(this, prefix) { |
55 | |
56 | @Override |
57 | protected KP buildPath(KP prefix, KP key) { |
58 | return AbstractPropertySet.this.buildPath(prefix, key); |
59 | } |
60 | |
61 | }; |
62 | } |
63 | |
64 | @Override |
65 | public void remove(KP key) { |
66 | throw new UnsupportedOperationException(); |
67 | } |
68 | |
69 | @Override |
70 | public void clear() { |
71 | throw new UnsupportedOperationException(); |
72 | } |
73 | |
74 | @Override |
75 | public void mount(KP prefix, PropertySet<KP> source) { |
76 | throw new UnsupportedOperationException(); |
77 | } |
78 | |
79 | @Override |
80 | public void unmount(KP prefix) { |
81 | throw new UnsupportedOperationException(); |
82 | } |
83 | |
84 | @Override |
85 | public void setAll(PropertySet<KP> source) { |
86 | throw new UnsupportedOperationException(); |
87 | } |
88 | |
89 | @Override |
90 | public Object get(KP key) { |
91 | for (PropertySet<KP> ch: shadows) { |
92 | if (ch!=null) { |
93 | Object ret = ch.get(key); |
94 | if (ret!=null) { |
95 | return ret; |
96 | } |
97 | } |
98 | } |
99 | return null; |
100 | } |
101 | |
102 | @Override |
103 | public Object get(KP key, Object defaultValue) { |
104 | for (PropertySet<KP> ch: shadows) { |
105 | if (ch!=null) { |
106 | Object ret = ch.get(key); |
107 | if (ret!=null) { |
108 | return ret; |
109 | } |
110 | } |
111 | } |
112 | return defaultValue; |
113 | } |
114 | |
115 | @Override |
116 | public <T> T get(KP key, Class<T> type) { |
117 | for (PropertySet<KP> ch: shadows) { |
118 | if (ch!=null) { |
119 | T ret = ch.get(key, type); |
120 | if (ret!=null) { |
121 | return ret; |
122 | } |
123 | } |
124 | } |
125 | return null; |
126 | } |
127 | |
128 | @Override |
129 | public <T> T get(KP key, Class<T> type, T defaultValue) { |
130 | for (PropertySet<KP> ch: shadows) { |
131 | if (ch!=null) { |
132 | T ret = ch.get(key, type); |
133 | if (ret!=null) { |
134 | return ret; |
135 | } |
136 | } |
137 | } |
138 | return defaultValue; |
139 | } |
140 | |
141 | @Override |
142 | public void set(KP key, Object value) { |
143 | throw new UnsupportedOperationException(); |
144 | |
145 | } |
146 | |
147 | @Override |
148 | public void setInvocable(KP key, Invocable<PropertySet<KP>, ?, KP> invocable) { |
149 | throw new UnsupportedOperationException(); |
150 | } |
151 | |
152 | @Override |
153 | public void cook(KP key, Callable<?> callable) { |
154 | throw new UnsupportedOperationException(); |
155 | } |
156 | |
157 | @Override |
158 | public void cook(KP key, Callable<?> callable, long delay, TimeUnit timeUnit) { |
159 | throw new UnsupportedOperationException(); |
160 | } |
161 | |
162 | @Override |
163 | public void cooking(KP key, Future<?> future) { |
164 | throw new UnsupportedOperationException(); |
165 | } |
166 | |
167 | @Override |
168 | public void lazy(KP key, Callable<?> callable) { |
169 | throw new UnsupportedOperationException(); |
170 | } |
171 | |
172 | @Override |
173 | public void cancel(KP key) { |
174 | throw new UnsupportedOperationException(); |
175 | } |
176 | |
177 | @Override |
178 | public void cancelAll() { |
179 | throw new UnsupportedOperationException(); |
180 | } |
181 | |
182 | @Override |
183 | public Object invoke(KP key, Object... args) { |
184 | throw new UnsupportedOperationException(); |
185 | } |
186 | |
187 | @Override |
188 | public Object shadowInvoke(KP key, Object... args) { |
189 | throw new UnsupportedOperationException(); |
190 | } |
191 | |
192 | @Override |
193 | public Object shadowGet(KP key) { |
194 | throw new UnsupportedOperationException(); |
195 | } |
196 | |
197 | @Override |
198 | public Object shadowGet(KP key, Object defaultValue) { |
199 | throw new UnsupportedOperationException(); |
200 | } |
201 | |
202 | @Override |
203 | public <T> T shadowGet(KP key, Class<T> type) { |
204 | throw new UnsupportedOperationException(); |
205 | } |
206 | |
207 | @Override |
208 | public <T> T shadowGet(KP key, Class<T> type, T defaultValue) { |
209 | throw new UnsupportedOperationException(); |
210 | } |
211 | |
212 | }; |
213 | |
214 | /** |
215 | * @return Store for values. |
216 | */ |
217 | protected abstract Map<KE, Object> getValueStore(); |
218 | |
219 | /** |
220 | * |
221 | * @return |
222 | */ |
223 | protected abstract Map<KE, AbstractPropertySet<KP,KE>> getSubSetStore(); |
224 | |
225 | /** |
226 | * Creates a property set which shares lock and chain with this one. |
227 | * @return |
228 | */ |
229 | protected abstract AbstractPropertySet<KP,KE> newInstance(KE key); |
230 | |
231 | protected abstract Map<KE, PropertySet<KP>> getMounts(); |
232 | |
233 | protected AbstractPropertySet( |
234 | ExecutorService executorService, |
235 | Converter converter, |
236 | // ClassLoader classLoader, |
237 | Context context, |
238 | PropertySet<KP>... shadows) { |
239 | this.executorService = executorService; |
240 | this.converter = converter==null ? ConvertingService.CONVERTER : converter; |
241 | // this.classLoader = classLoader==null ? getClass().getClassLoader() : classLoader; |
242 | this.context = context; |
243 | this.shadows = shadows; |
244 | } |
245 | |
246 | protected abstract KP buildPath(int startIdx, Object... elements); |
247 | |
248 | protected KP buildPath(Object... elements) { |
249 | return buildPath(0, elements); |
250 | } |
251 | |
252 | protected abstract KP buildPath(KP prefix, Object... elements); |
253 | |
254 | protected abstract KP buildPath(KE prefix, KP suffix); |
255 | |
256 | protected abstract KE[] tokenize(KP path); |
257 | |
258 | /** |
259 | * Versions object for forward-only change propagation purposes. |
260 | * @param <T> |
261 | * @param source |
262 | * @return |
263 | */ |
264 | protected abstract <T> T version(T source); |
265 | |
266 | @Override |
267 | public Set<KP> keySet() { |
268 | readLock().lock(); |
269 | HashSet<KP> ret = new HashSet<KP>(); |
270 | try { |
271 | for (KE key: getValueStore().keySet()) { |
272 | ret.add(buildPath(key)); |
273 | } |
274 | |
275 | for (Map.Entry<KE, PropertySet<KP>> entry: getMounts().entrySet()) { |
276 | for (KP path: entry.getValue().keySet()) { |
277 | ret.add(buildPath(entry.getKey(), path)); |
278 | } |
279 | } |
280 | |
281 | for (Map.Entry<KE, AbstractPropertySet<KP, KE>> entry: getSubSetStore().entrySet()) { |
282 | if (!getMounts().containsKey(entry.getKey())) { // If not shadowed |
283 | for (KP path: entry.getValue().keySet()) { |
284 | ret.add(buildPath(entry.getKey(), path)); |
285 | } |
286 | } |
287 | } |
288 | } finally { |
289 | readLock().unlock(); |
290 | } |
291 | |
292 | ret.addAll(shadowPropertySet.keySet()); |
293 | return ret; |
294 | } |
295 | |
296 | @Override |
297 | public PropertySet<KP> subset(KP prefix) { |
298 | writeLock().lock(); |
299 | try { |
300 | return subset(tokenize(prefix),0,true); |
301 | } finally { |
302 | writeLock().unlock(); |
303 | } |
304 | } |
305 | |
306 | protected PropertySet<KP> subset(KE[] prefix, int idx, boolean create) { |
307 | PropertySet<KP> mount = getMounts().get(prefix[idx]); |
308 | if (mount!=null) { |
309 | if (prefix.length==idx+1) { |
310 | return mount; |
311 | } |
312 | |
313 | return mount.subset(buildPath(idx+1, prefix)); |
314 | } |
315 | |
316 | AbstractPropertySet<KP, KE> ret = getSubSetStore().get(prefix[idx]); |
317 | if (ret==null) { |
318 | if (!create) { |
319 | return null; |
320 | } |
321 | ret = newInstance(prefix[idx]); |
322 | getSubSetStore().put(prefix[idx], ret); |
323 | } |
324 | |
325 | if (prefix.length==idx+1) { |
326 | return ret; |
327 | } |
328 | |
329 | return ret.subset(prefix, idx+1, create); |
330 | } |
331 | |
332 | @Override |
333 | public void remove(KP key) { |
334 | writeLock().lock(); |
335 | try { |
336 | remove(tokenize(key), 0); |
337 | } finally { |
338 | writeLock().unlock(); |
339 | } |
340 | } |
341 | |
342 | protected void remove(KE[] path, int idx) { |
343 | if (path.length==idx+1) { |
344 | getValueStore().remove(path[idx]); |
345 | } else { |
346 | PropertySet<KP> mount = getMounts().get(path[idx]); |
347 | if (mount!=null) { |
348 | mount.remove(buildPath(idx+1, path)); |
349 | } else { |
350 | AbstractPropertySet<KP, KE> ss = getSubSetStore().get(path[idx]); |
351 | if (ss!=null) { |
352 | ss.remove(path, idx+1); |
353 | } |
354 | } |
355 | } |
356 | } |
357 | |
358 | @Override |
359 | public void clear() { |
360 | writeLock().lock(); |
361 | try { |
362 | getValueStore().clear(); |
363 | for (PropertySet<KP> m: getMounts().values()) { |
364 | m.clear(); |
365 | } |
366 | for (Map.Entry<KE, AbstractPropertySet<KP, KE>> e: getSubSetStore().entrySet()) { |
367 | if (!getMounts().containsKey(e.getKey())) { |
368 | e.getValue().clear(); |
369 | } |
370 | } |
371 | for (int i=0; i<shadows.length; ++i) { |
372 | shadows[i]=null; |
373 | } |
374 | } finally { |
375 | writeLock().unlock(); |
376 | } |
377 | } |
378 | |
379 | @Override |
380 | public void mount(KP prefix, PropertySet<KP> source) { |
381 | writeLock().lock(); |
382 | try { |
383 | mount(tokenize(prefix), 0, source); |
384 | } finally { |
385 | writeLock().unlock(); |
386 | } |
387 | } |
388 | |
389 | protected void mount(KE[] path, int idx, PropertySet<KP> source) { |
390 | if (path.length==idx+1) { |
391 | getMounts().put(path[idx], source); |
392 | } else { |
393 | AbstractPropertySet<KP, KE> ss = getSubSetStore().get(path[idx]); |
394 | if (ss==null) { |
395 | ss = newInstance(path[idx]); |
396 | getSubSetStore().put(path[idx], ss); |
397 | } |
398 | |
399 | ss.mount(path, idx+1, source); |
400 | } |
401 | } |
402 | |
403 | @Override |
404 | public void unmount(KP prefix) { |
405 | writeLock().lock(); |
406 | try { |
407 | unmount(tokenize(prefix), 0); |
408 | } finally { |
409 | writeLock().unlock(); |
410 | } |
411 | } |
412 | |
413 | protected void unmount(KE[] path, int idx) { |
414 | if (path.length==idx+1) { |
415 | getMounts().remove(path[idx]); |
416 | } else { |
417 | AbstractPropertySet<KP, KE> ss = getSubSetStore().get(path[idx]); |
418 | if (ss!=null) { |
419 | ss.unmount(path, idx+1); |
420 | } |
421 | } |
422 | } |
423 | |
424 | @Override |
425 | public void setAll(PropertySet<KP> source) { |
426 | writeLock().lock(); |
427 | try { |
428 | for (KP path: source.keySet()) { |
429 | set(path, source.get(path)); |
430 | } |
431 | } finally { |
432 | writeLock().unlock(); |
433 | } |
434 | } |
435 | |
436 | @Override |
437 | public Object get(KP key) { |
438 | Object ret; |
439 | Object[] va = {null}; |
440 | readLock().lock(); |
441 | try { |
442 | ret = get(tokenize(key), 0, va); |
443 | } finally { |
444 | readLock().unlock(); |
445 | } |
446 | |
447 | // Chained version |
448 | if (va[0]!=null) { |
449 | set(key, va[0]); |
450 | } |
451 | |
452 | return ret; |
453 | } |
454 | |
455 | private static class CookEntry { |
456 | |
457 | CookEntry(Future<?> future, Callable<?> callable) { |
458 | this.future = future; |
459 | this.callable = callable; |
460 | } |
461 | |
462 | Future<?> future; |
463 | Callable<?> callable; |
464 | Object value; |
465 | |
466 | synchronized Object get() throws Exception { |
467 | if (future!=null) { |
468 | value = future.get(); |
469 | future = null; |
470 | } else if (callable!=null) { |
471 | value = callable.call(); |
472 | callable = null; |
473 | } |
474 | return value; |
475 | } |
476 | |
477 | void cancel() { |
478 | if (future!=null && callable!=null && !future.isDone()) { |
479 | future.cancel(false); |
480 | future = null; |
481 | } |
482 | } |
483 | |
484 | } |
485 | |
486 | protected Object get(KE[] path, int idx, Object[] va) { |
487 | if (path.length==idx+1) { |
488 | if (getValueStore().containsKey(path[idx])) { |
489 | Object ret = getValueStore().get(path[idx]); |
490 | |
491 | if (ret instanceof Invocable) { |
492 | Invocable<PropertySet<KP>,?, KP> i = (Invocable<PropertySet<KP>,?, KP>) ret; |
493 | if (i.getParameterTypes()==null || i.getParameterTypes().length==0) { |
494 | InvocationImpl<KP> invocation = new InvocationImpl<KP>(); |
495 | invocation.setPropertySet(this); |
496 | return i.invoke(this, invocation, executorService); |
497 | } |
498 | return i; |
499 | } |
500 | |
501 | if (ret instanceof CookEntry) { |
502 | try { |
503 | ret = ((CookEntry) ret).get(); |
504 | va[0] = ret; // To replace cook entry with value |
505 | return ret; |
506 | } catch (Exception e) { |
507 | throw new PropertySetException(e); |
508 | } |
509 | } |
510 | |
511 | return ret; |
512 | } |
513 | } else { |
514 | PropertySet<KP> mount = getMounts().get(path[idx]); |
515 | if (mount!=null) { |
516 | return mount.get(buildPath(idx+1, path)); |
517 | } |
518 | |
519 | AbstractPropertySet<KP, KE> ss = getSubSetStore().get(path[idx]); |
520 | if (ss!=null) { |
521 | Object ret = ss.get(path, idx+1, va); |
522 | if (ret!=null) { |
523 | return ret; |
524 | } |
525 | } |
526 | } |
527 | |
528 | va[0] = shadowGet(buildPath(idx, path)); |
529 | return va[0]; |
530 | } |
531 | |
532 | @Override |
533 | public Object get(KP key, Object defaultValue) { |
534 | Object ret = get(key); |
535 | return ret == null ? defaultValue : ret; |
536 | } |
537 | |
538 | @Override |
539 | public <T> T get(KP key, Class<T> type) { |
540 | return get(key, type, null); |
541 | } |
542 | |
543 | @Override |
544 | public <T> T get(KP key, Class<T> type, T defaultValue) { |
545 | Object ret = get(key); |
546 | if (ret==null) { |
547 | if (type!=null && type.isInterface()) { |
548 | PropertySet<KP> toWrap = subset(tokenize(key), 0, false); |
549 | if (toWrap!=null) { |
550 | return toWrap.getAdapter(type, null); |
551 | } |
552 | } |
553 | return defaultValue; |
554 | } |
555 | if (type.isInstance(ret)) { |
556 | return (T) ret; |
557 | } |
558 | T converted = converter.convert(ret, type, context); |
559 | if (converted==null) { |
560 | throw new PropertySetException("Cannot convert "+ ret +" to "+type); |
561 | } |
562 | return converted; |
563 | } |
564 | |
565 | @Override |
566 | public void set(KP key, Object value) { |
567 | writeLock().lock(); |
568 | try { |
569 | set(tokenize(key), 0, value); |
570 | } finally { |
571 | writeLock().unlock(); |
572 | } |
573 | } |
574 | |
575 | protected void set(KE[] path, int idx, Object value) { |
576 | if (path.length==idx+1) { |
577 | getValueStore().put(path[idx], value); |
578 | } else { |
579 | PropertySet<KP> mount = getMounts().get(path[idx]); |
580 | if (mount!=null) { |
581 | mount.set(buildPath(idx+1, path), value); |
582 | } else { |
583 | AbstractPropertySet<KP, KE> ret = getSubSetStore().get(path[idx]); |
584 | if (ret==null) { |
585 | ret = newInstance(path[idx]); |
586 | getSubSetStore().put(path[idx], ret); |
587 | } |
588 | |
589 | ret.set(path, idx+1, value); |
590 | } |
591 | } |
592 | } |
593 | |
594 | @Override |
595 | public void setInvocable(KP key, Invocable<PropertySet<KP>, ?, KP> invocable) { |
596 | set(key, invocable); |
597 | } |
598 | |
599 | @Override |
600 | public void cook(KP key, Callable<?> callable) { |
601 | cook(key, callable, 0, null); |
602 | } |
603 | |
604 | protected void cook(KE[] path, int idx, Callable<?> callable, long delay, TimeUnit timeUnit) { |
605 | if (path.length==idx+1) { |
606 | Future<?> future = null; |
607 | if (delay>0 && timeUnit!=null && executorService instanceof ScheduledExecutorService) { |
608 | future = ((ScheduledExecutorService) executorService).schedule(callable, delay, timeUnit); |
609 | } else if (executorService!=null) { |
610 | future = executorService.submit(callable); |
611 | } |
612 | getValueStore().put(path[idx], new CookEntry(future, callable)); |
613 | } else { |
614 | PropertySet<KP> mount = getMounts().get(path[idx]); |
615 | if (mount!=null) { |
616 | mount.cook(buildPath(idx+1, path), callable, delay, timeUnit); |
617 | } |
618 | |
619 | AbstractPropertySet<KP, KE> ret = getSubSetStore().get(path[idx]); |
620 | if (ret==null) { |
621 | ret = newInstance(path[idx]); |
622 | getSubSetStore().put(path[idx], ret); |
623 | } |
624 | |
625 | ret.cook(path, idx+1, callable, delay, timeUnit); |
626 | } |
627 | } |
628 | |
629 | @Override |
630 | public void cook(KP key, Callable<?> callable, long delay, TimeUnit timeUnit) { |
631 | writeLock().lock(); |
632 | try { |
633 | cook(tokenize(key), 0, callable, delay, timeUnit); |
634 | } finally { |
635 | writeLock().unlock(); |
636 | } |
637 | } |
638 | |
639 | @Override |
640 | public void cooking(KP key, Future<?> future) { |
641 | set(key, new CookEntry(future, null)); |
642 | } |
643 | |
644 | @Override |
645 | public void lazy(KP key, Callable<?> callable) { |
646 | set(key, new CookEntry(null, callable)); |
647 | } |
648 | |
649 | @Override |
650 | public void cancel(KP key) { |
651 | readLock().lock(); |
652 | try { |
653 | cancel(tokenize(key),0); |
654 | } finally { |
655 | readLock().unlock(); |
656 | } |
657 | } |
658 | |
659 | protected void cancel(KE[] path, int idx) { |
660 | if (path.length==idx+1) { |
661 | Object v = getValueStore().get(path[idx]); |
662 | if (v instanceof CookEntry) { |
663 | ((CookEntry) v).cancel(); |
664 | } |
665 | } else { |
666 | PropertySet<KP> mount = getMounts().get(path[idx]); |
667 | if (mount!=null) { |
668 | mount.cancel(buildPath(idx+1, path)); |
669 | } |
670 | |
671 | AbstractPropertySet<KP, KE> ret = getSubSetStore().get(path[idx]); |
672 | if (ret!=null) { |
673 | ret.cancel(path, idx+1); |
674 | } |
675 | } |
676 | } |
677 | |
678 | @Override |
679 | public void cancelAll() { |
680 | readLock().lock(); |
681 | try { |
682 | for (PropertySet<KP> mount: getMounts().values()) { |
683 | mount.cancelAll(); |
684 | } |
685 | for (PropertySet<KP> ss: getSubSetStore().values()) { |
686 | ss.cancelAll(); |
687 | } |
688 | for (Object v: getValueStore().values()) { |
689 | if (v instanceof CookEntry) { |
690 | ((CookEntry) v).cancel(); |
691 | } |
692 | } |
693 | } finally { |
694 | readLock().unlock(); |
695 | } |
696 | } |
697 | |
698 | @Override |
699 | public Object invoke(KP key, Object... args) { |
700 | return invoke(this, key, args); |
701 | } |
702 | |
703 | private Object invoke(PropertySet<KP> contextPropertySet, KP key, Object... args) { |
704 | writeLock().lock(); // To avoid deadlocks if invocable has side effects. |
705 | try { |
706 | Object i = contextPropertySet.get(key); |
707 | if (i instanceof Invocable) { |
708 | Invocable invocable = (Invocable) i; |
709 | Class[] parameterTypes = invocable.getParameterTypes(); |
710 | if (args!=null && parameterTypes!=null) { |
711 | if (parameterTypes.length!=args.length) { |
712 | throw new PropertySetException("Cannot invoke "+key+" - invalid number of arguments"); |
713 | } |
714 | args = Arrays.copyOf(args, args.length); |
715 | for (int j=0; j<args.length; ++j) { |
716 | if (args[j]!=null && !parameterTypes[j].isInstance(args[j])) { |
717 | Object newVal = converter.convert(args[j], parameterTypes[j], context); |
718 | if (args[j]==null) { |
719 | throw new PropertySetException("Cannot covert "+args[j]+" to "+parameterTypes[j]); |
720 | } |
721 | args[j] = newVal; |
722 | } |
723 | } |
724 | } |
725 | InvocationImpl invocation = new InvocationImpl(args, contextPropertySet, null); |
726 | return invocable.invoke(contextPropertySet, invocation, executorService); |
727 | } |
728 | |
729 | throw new PropertySetException("Not invocable: "+i); |
730 | } finally { |
731 | writeLock().unlock(); |
732 | } |
733 | } |
734 | |
735 | @Override |
736 | public Object shadowInvoke(KP key, Object... args) { |
737 | return invoke(shadowPropertySet, key, args); |
738 | } |
739 | |
740 | @Override |
741 | public Object shadowGet(KP key) { |
742 | return shadowPropertySet.get(key); |
743 | } |
744 | |
745 | @Override |
746 | public Object shadowGet(KP key, Object defaultValue) { |
747 | return shadowPropertySet.get(key, defaultValue); |
748 | } |
749 | |
750 | @Override |
751 | public <T> T shadowGet(KP key, Class<T> type) { |
752 | return shadowPropertySet.get(key, type); |
753 | } |
754 | |
755 | @Override |
756 | public <T> T shadowGet(KP key, Class<T> type, T defaultValue) { |
757 | return shadowPropertySet.get(key, type, defaultValue); |
758 | } |
759 | |
760 | |
761 | } |