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.Arrays;
39 import java.util.Collection;
40 import java.util.List;
41 import java.util.Map;
42
43 import com.google.common.collect.Lists;
44 import com.google.common.collect.Maps;
45 import com.google.common.collect.Sets;
46
47
48
49
50 public class ConfigurationMapOverlay {
51
52 private Map<String, ?> source;
53 private Map<String, ?> decorator;
54 private String overlayPath;
55
56 public static ConfigurationMapOverlay of(Map<String, ?> source) {
57 ConfigurationMapOverlay configMapMerger = new ConfigurationMapOverlay();
58 configMapMerger.source = source;
59 return configMapMerger;
60 }
61
62 public ConfigurationMapOverlay by(Map<String,?> decorator) {
63 this.decorator = decorator;
64 return this;
65 }
66
67 public ConfigurationMapOverlay at(String path) {
68 this.overlayPath = path;
69 return this;
70 }
71
72 public Map<String, Object> overlay() {
73 return doOverlay(source, "/");
74 }
75
76
77
78
79
80
81
82
83
84 private Map<String, Object> doOverlay(Map<String, ?> source, String currentTraversedPath) {
85 Map<String, Object> result = Maps.newLinkedHashMap();
86 for (final String key : source.keySet()) {
87 final Object value = source.get(key);
88
89 if (!(value instanceof Collection) && !(value instanceof Map)) {
90 result.put(key, value);
91 } else {
92
93 final Map<String, Object> map = ToMap.toMap(value);
94 result.put(key, doOverlay(map, String.format("%s/%s", stripEnd(currentTraversedPath, "/"), key)));
95 }
96 }
97
98 if (overlayPath.equals(currentTraversedPath)) {
99 result = mergeMaps(result, decorator);
100 } else {
101
102
103
104 if (overlayPath.startsWith(currentTraversedPath)) {
105
106
107 String notCoveredOverlayPath = removeStart(overlayPath, currentTraversedPath);
108
109
110 notCoveredOverlayPath = stripStart(notCoveredOverlayPath, "/");
111
112
113 final String nextNotCoveredLocation = notCoveredOverlayPath.contains("/") ? substringBefore(notCoveredOverlayPath, "/") : notCoveredOverlayPath;
114
115
116 if (!source.containsKey(nextNotCoveredLocation)) {
117 addWithoutMerging(result, decorator, notCoveredOverlayPath);
118 }
119 }
120 }
121
122 return result;
123 }
124
125 private void addWithoutMerging(Map source, Map decorator, String pathToAdd) {
126
127 Map<String, Object> configMap;
128 if (pathToAdd.contains("/")) {
129 configMap = Maps.newLinkedHashMap();
130
131
132
133
134
135 final List<String> fragmentsToCreate = Lists.reverse(Arrays.asList(substringAfter(pathToAdd, "/").split("/")));
136 for (final String pathFragment : fragmentsToCreate) {
137 if (configMap.isEmpty()) {
138 configMap.put(pathFragment, decorator);
139 } else {
140 Map<String, Object> wrappingMap = Maps.newLinkedHashMap();
141 wrappingMap.put(pathFragment, configMap);
142 configMap = wrappingMap;
143 }
144 }
145 } else {
146 configMap = decorator;
147 }
148
149 final String key = substringBefore(pathToAdd, "/");
150
151 source.put(key, configMap);
152 }
153
154 private Map<String, Object> mergeMaps(Map<String, ?> original, Map<String, ?> decorator) {
155 final Map<String, Object> result = Maps.newLinkedHashMap();
156 for (final String key : Sets.union(original.keySet(), decorator.keySet())) {
157 result.put(key, mergeValues(original.get(key), decorator.get(key)));
158 }
159 return result;
160 }
161
162 private Object mergeValues(Object source, Object decoration) {
163 if (source == null) {
164 return decoration;
165 }
166
167 if (decoration == null) {
168 return source;
169 }
170
171 if (!(source instanceof Collection) && !(source instanceof Map)) {
172
173 return decoration;
174 } else {
175
176 final Map<String, Object> sourceValueMap = ToMap.toMap(source);
177 final Map<String, Object> decorationMap = ToMap.toMap(decoration);
178 return mergeMaps(sourceValueMap, decorationMap);
179 }
180 }
181 }