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.HashSet;
42 import java.util.LinkedHashMap;
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, ?> source;
56 private Map<String, ?> decorator;
57 private String overlayPath;
58
59 public static ConfigurationMapOverlay of(Map<String, ?> source) {
60 ConfigurationMapOverlay configMapMerger = new ConfigurationMapOverlay();
61 configMapMerger.source = source;
62 return configMapMerger;
63 }
64
65 public ConfigurationMapOverlay by(Map<String, ?> 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 Map<String, Object> doOverlay(Map<String, ?> source, String currentTraversedPath) {
88 Map<String, Object> result = new LinkedHashMap<>();
89 for (final String key : source.keySet()) {
90 final Object value = source.get(key);
91
92 if (!(value instanceof Collection) && !(value instanceof Map)) {
93 result.put(key, value);
94 } else {
95
96 final Map<String, 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 result = mergeMaps(result, decorator);
103 } else {
104
105
106
107 if (overlayPath.startsWith("/".equals(currentTraversedPath) ? "/" : currentTraversedPath + "/")) {
108
109
110 String notCoveredOverlayPath = removeStart(overlayPath, currentTraversedPath);
111
112
113 notCoveredOverlayPath = stripStart(notCoveredOverlayPath, "/");
114
115
116 final String nextNotCoveredLocation = notCoveredOverlayPath.contains("/") ? substringBefore(notCoveredOverlayPath, "/") : notCoveredOverlayPath;
117
118
119 if (!source.containsKey(nextNotCoveredLocation)) {
120 addWithoutMerging(result, decorator, notCoveredOverlayPath);
121 }
122 }
123 }
124
125 return result;
126 }
127
128 private void addWithoutMerging(Map source, Map decorator, String pathToAdd) {
129
130 Map<String, Object> configMap;
131 if (pathToAdd.contains("/")) {
132 configMap = new LinkedHashMap<>();
133
134
135
136
137
138 final List<String> fragmentsToCreate = Lists.reverse(Arrays.asList(substringAfter(pathToAdd, "/").split("/")));
139 for (final String pathFragment : fragmentsToCreate) {
140 if (configMap.isEmpty()) {
141 configMap.put(pathFragment, decorator);
142 } else {
143 Map<String, Object> wrappingMap = new LinkedHashMap<>();
144 wrappingMap.put(pathFragment, configMap);
145 configMap = wrappingMap;
146 }
147 }
148 } else {
149 configMap = decorator;
150 }
151
152 final String key = substringBefore(pathToAdd, "/");
153
154 source.put(key, configMap);
155 }
156
157 private Map<String, Object> mergeMaps(Map<String, ?> original, Map<String, ?> decorator) {
158 final Map<String, Object> result = new LinkedHashMap<>();
159
160 final Set<String> resultingKeys;
161 final Set<String> originalKeys = original.keySet();
162 final Set<String> decoratingKeys = decorator.keySet();
163
164 if (decorator instanceof OverridingMap) {
165
166 resultingKeys = new HashSet<>(decoratingKeys);
167 } else {
168
169 resultingKeys = Sets.union(originalKeys, decorator.keySet());
170 }
171
172 resultingKeys.forEach(key -> result.put(key, mergeValues(original.get(key), decorator.get(key))));
173
174 return result;
175 }
176
177 private Object mergeValues(Object source, Object decoration) {
178 if (source == null) {
179 return decoration;
180 }
181
182 if (decoration == null) {
183 return source;
184 }
185
186 if (!(source instanceof Collection) && !(source instanceof Map)) {
187
188 return decoration;
189 } else {
190
191 final Map<String, Object> sourceValueMap = ToMap.toMap(source);
192 final Map<String, Object> decorationMap = ToMap.toMap(decoration);
193 return mergeMaps(sourceValueMap, decorationMap);
194 }
195 }
196
197
198
199
200
201
202
203
204
205 public final static class OverridingMap<K, V> extends AbstractMap<K, V> {
206
207 private final Map<K, V> wrappedMap;
208
209 public OverridingMap(Map<K, V> wrappedMap) {
210 this.wrappedMap = wrappedMap;
211 }
212
213 @Override
214 public Set<Entry<K, V>> entrySet() {
215 return this.wrappedMap.entrySet();
216 }
217 }
218
219 }