1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.config.maputil;
35
36 import static org.apache.commons.lang3.StringUtils.*;
37
38 import java.util.AbstractMap;
39 import java.util.Arrays;
40 import java.util.Collection;
41 import java.util.LinkedHashMap;
42 import java.util.LinkedHashSet;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46
47 import com.google.common.collect.Lists;
48 import com.google.common.collect.Sets;
49
50
51
52
53 public class ConfigurationMapOverlay {
54
55 private Map<String, Object> source;
56 private Map<String, Object> decorator;
57 private String overlayPath;
58
59 public static ConfigurationMapOverlay of(Map<String, Object> source) {
60 ConfigurationMapOverlay configMapMerger = new ConfigurationMapOverlay();
61 configMapMerger.source = source;
62 return configMapMerger;
63 }
64
65 public ConfigurationMapOverlay by(Map<String, Object> decorator) {
66 this.decorator = decorator;
67 return this;
68 }
69
70 public ConfigurationMapOverlay at(String path) {
71 this.overlayPath = path;
72 return this;
73 }
74
75 public Map<String, Object> overlay() {
76 return doOverlay(source, "/");
77 }
78
79
80
81
82
83
84
85
86
87 private <K> Map<K, Object> doOverlay(Map<K, Object> source, String currentTraversedPath) {
88 Map<K, Object> result = new LinkedHashMap<>();
89 for (K key : source.keySet()) {
90 Object value = source.get(key);
91
92 if (!(value instanceof Collection) && !(value instanceof Map)) {
93 result.put(key, value);
94 } else {
95
96 Map<Object, Object> map = ToMap.toMap(value);
97 result.put(key, doOverlay(map, String.format("%s/%s", stripEnd(currentTraversedPath, "/"), key)));
98 }
99 }
100
101 if (overlayPath.equals(currentTraversedPath)) {
102
103 result = mergeMaps((Map) result, decorator);
104 } else {
105
106
107
108 if (overlayPath.startsWith("/".equals(currentTraversedPath) ? "/" : currentTraversedPath + "/")) {
109
110
111 String notCoveredOverlayPath = removeStart(overlayPath, currentTraversedPath);
112
113
114 notCoveredOverlayPath = stripStart(notCoveredOverlayPath, "/");
115
116
117 final String nextNotCoveredLocation = notCoveredOverlayPath.contains("/") ? substringBefore(notCoveredOverlayPath, "/") : notCoveredOverlayPath;
118
119
120 if (!source.containsKey(nextNotCoveredLocation)) {
121 addWithoutMerging(result, decorator, notCoveredOverlayPath);
122 }
123 }
124 }
125
126 return result;
127 }
128
129 private void addWithoutMerging(Map source, Map decorator, String pathToAdd) {
130
131 Map<String, Object> configMap;
132 if (pathToAdd.contains("/")) {
133 configMap = new LinkedHashMap<>();
134
135
136
137
138
139 final List<String> fragmentsToCreate = Lists.reverse(Arrays.asList(substringAfter(pathToAdd, "/").split("/")));
140 for (final String pathFragment : fragmentsToCreate) {
141 if (configMap.isEmpty()) {
142 configMap.put(pathFragment, decorator);
143 } else {
144 Map<String, Object> wrappingMap = new LinkedHashMap<>();
145 wrappingMap.put(pathFragment, configMap);
146 configMap = wrappingMap;
147 }
148 }
149 } else {
150 configMap = decorator;
151 }
152
153 final String key = substringBefore(pathToAdd, "/");
154
155 source.put(key, configMap);
156 }
157
158 private <K> Map<K, Object> mergeMaps(Map<K, Object> original, Map<K, Object> decorator) {
159 final Map<K, Object> result = new LinkedHashMap<>();
160
161 final Set<K> resultingKeys;
162 final Set<K> originalKeys = original.keySet();
163 final Set<K> decoratingKeys = decorator.keySet();
164
165 if (decorator instanceof OverridingMap) {
166
167 resultingKeys = new LinkedHashSet<>(decoratingKeys);
168 } else {
169
170 resultingKeys = Sets.union(originalKeys, decorator.keySet());
171 }
172
173 resultingKeys.forEach(key -> result.put(key, mergeValues(original.get(key), decorator.get(key))));
174
175 return result;
176 }
177
178 private Object mergeValues(Object source, Object decoration) {
179 if (source == null) {
180 return decoration;
181 }
182
183 if (decoration == null) {
184 return source;
185 }
186
187 if (!(source instanceof Collection) && !(source instanceof Map)) {
188
189 return decoration;
190 } else {
191
192 final Map<Object, Object> sourceValueMap = ToMap.toMap(source);
193 final Map<Object, Object> decorationMap = ToMap.toMap(decoration);
194 return mergeMaps(sourceValueMap, decorationMap);
195 }
196 }
197
198
199
200
201
202
203
204
205
206 public final static class OverridingMap<K, V> extends AbstractMap<K, V> {
207
208 private final Map<K, V> wrappedMap;
209
210 public OverridingMap(Map<K, V> wrappedMap) {
211 this.wrappedMap = wrappedMap;
212 }
213
214 @Override
215 public Set<Entry<K, V>> entrySet() {
216 return this.wrappedMap.entrySet();
217 }
218 }
219
220 }