1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.model.inheritance;
20
21 import javax.inject.Named;
22 import javax.inject.Singleton;
23
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29
30 import org.apache.maven.api.model.InputLocation;
31 import org.apache.maven.api.model.Model;
32 import org.apache.maven.api.model.ModelBase;
33 import org.apache.maven.api.model.Plugin;
34 import org.apache.maven.api.model.PluginContainer;
35 import org.apache.maven.api.model.ReportPlugin;
36 import org.apache.maven.api.model.Reporting;
37 import org.apache.maven.model.building.ModelBuildingRequest;
38 import org.apache.maven.model.building.ModelProblemCollector;
39 import org.apache.maven.model.merge.MavenModelMerger;
40
41
42
43
44
45 @SuppressWarnings({"checkstyle:methodname"})
46 @Named
47 @Singleton
48 public class DefaultInheritanceAssembler implements InheritanceAssembler {
49
50 private static final String CHILD_DIRECTORY = "child-directory";
51
52 private static final String CHILD_DIRECTORY_PROPERTY = "project.directory";
53
54 private final InheritanceModelMerger merger = new InheritanceModelMerger();
55
56 @Override
57 public Model assembleModelInheritance(
58 Model child, Model parent, ModelBuildingRequest request, ModelProblemCollector problems) {
59 Map<Object, Object> hints = new HashMap<>();
60 String childPath = child.getProperties().getOrDefault(CHILD_DIRECTORY_PROPERTY, child.getArtifactId());
61 hints.put(CHILD_DIRECTORY, childPath);
62 hints.put(MavenModelMerger.CHILD_PATH_ADJUSTMENT, getChildPathAdjustment(child, parent, childPath));
63 return merger.merge(child, parent, false, hints);
64 }
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85 private String getChildPathAdjustment(Model child, Model parent, String childDirectory) {
86 String adjustment = "";
87
88 if (parent != null) {
89 String childName = child.getArtifactId();
90
91
92
93
94
95
96
97
98 if (child.getProjectDirectory() != null) {
99 childName = child.getProjectDirectory().getFileName().toString();
100 }
101
102 for (String module : parent.getModules()) {
103 module = module.replace('\\', '/');
104
105 if (module.regionMatches(true, module.length() - 4, ".xml", 0, 4)) {
106 module = module.substring(0, module.lastIndexOf('/') + 1);
107 }
108
109 String moduleName = module;
110 if (moduleName.endsWith("/")) {
111 moduleName = moduleName.substring(0, moduleName.length() - 1);
112 }
113
114 int lastSlash = moduleName.lastIndexOf('/');
115
116 moduleName = moduleName.substring(lastSlash + 1);
117
118 if ((moduleName.equals(childName) || (moduleName.equals(childDirectory))) && lastSlash >= 0) {
119 adjustment = module.substring(0, lastSlash);
120 break;
121 }
122 }
123 }
124
125 return adjustment;
126 }
127
128
129
130
131 protected static class InheritanceModelMerger extends MavenModelMerger {
132
133 @Override
134 protected String extrapolateChildUrl(String parentUrl, boolean appendPath, Map<Object, Object> context) {
135 Object childDirectory = context.get(CHILD_DIRECTORY);
136 Object childPathAdjustment = context.get(CHILD_PATH_ADJUSTMENT);
137
138 boolean isBlankParentUrl = true;
139
140 if (parentUrl != null) {
141 for (int i = 0; i < parentUrl.length(); i++) {
142 if (!Character.isWhitespace(parentUrl.charAt(i))) {
143 isBlankParentUrl = false;
144 }
145 }
146 }
147
148 if (isBlankParentUrl || childDirectory == null || childPathAdjustment == null || !appendPath) {
149 return parentUrl;
150 }
151
152
153 return appendPath(parentUrl, childDirectory.toString(), childPathAdjustment.toString());
154 }
155
156 private String appendPath(String parentUrl, String childPath, String pathAdjustment) {
157 StringBuilder url = new StringBuilder(parentUrl.length()
158 + pathAdjustment.length()
159 + childPath.length()
160 + ((pathAdjustment.length() == 0) ? 1 : 2));
161
162 url.append(parentUrl);
163 concatPath(url, pathAdjustment);
164 concatPath(url, childPath);
165
166 return url.toString();
167 }
168
169 private void concatPath(StringBuilder url, String path) {
170 if (!path.isEmpty()) {
171 boolean initialUrlEndsWithSlash = url.charAt(url.length() - 1) == '/';
172 boolean pathStartsWithSlash = path.charAt(0) == '/';
173
174 if (pathStartsWithSlash) {
175 if (initialUrlEndsWithSlash) {
176
177 url.setLength(url.length() - 1);
178 }
179 } else if (!initialUrlEndsWithSlash) {
180
181 url.append('/');
182 }
183
184 url.append(path);
185
186
187 if (initialUrlEndsWithSlash && !path.endsWith("/")) {
188 url.append('/');
189 }
190 }
191 }
192
193 @Override
194 protected void mergeModelBase_Properties(
195 ModelBase.Builder builder,
196 ModelBase target,
197 ModelBase source,
198 boolean sourceDominant,
199 Map<Object, Object> context) {
200 Map<String, String> merged = new HashMap<>();
201 if (sourceDominant) {
202 merged.putAll(target.getProperties());
203 putAll(merged, source.getProperties(), CHILD_DIRECTORY_PROPERTY);
204 } else {
205 putAll(merged, source.getProperties(), CHILD_DIRECTORY_PROPERTY);
206 merged.putAll(target.getProperties());
207 }
208 builder.properties(merged);
209 builder.location(
210 "properties",
211 InputLocation.merge(
212 target.getLocation("properties"), source.getLocation("properties"), sourceDominant));
213 }
214
215 private void putAll(Map<String, String> s, Map<String, String> t, Object excludeKey) {
216 for (Map.Entry<String, String> e : t.entrySet()) {
217 if (!e.getKey().equals(excludeKey)) {
218 s.put(e.getKey(), e.getValue());
219 }
220 }
221 }
222
223 @Override
224 protected void mergePluginContainer_Plugins(
225 PluginContainer.Builder builder,
226 PluginContainer target,
227 PluginContainer source,
228 boolean sourceDominant,
229 Map<Object, Object> context) {
230 List<Plugin> src = source.getPlugins();
231 if (!src.isEmpty()) {
232 List<Plugin> tgt = target.getPlugins();
233 Map<Object, Plugin> master = new LinkedHashMap<>(src.size() * 2);
234
235 for (Plugin element : src) {
236 if (element.isInherited() || !element.getExecutions().isEmpty()) {
237
238 Plugin plugin = Plugin.newInstance(false);
239 plugin = mergePlugin(plugin, element, sourceDominant, context);
240
241 Object key = getPluginKey().apply(plugin);
242
243 master.put(key, plugin);
244 }
245 }
246
247 Map<Object, List<Plugin>> predecessors = new LinkedHashMap<>();
248 List<Plugin> pending = new ArrayList<>();
249 for (Plugin element : tgt) {
250 Object key = getPluginKey().apply(element);
251 Plugin existing = master.get(key);
252 if (existing != null) {
253 element = mergePlugin(element, existing, sourceDominant, context);
254
255 master.put(key, element);
256
257 if (!pending.isEmpty()) {
258 predecessors.put(key, pending);
259 pending = new ArrayList<>();
260 }
261 } else {
262 pending.add(element);
263 }
264 }
265
266 List<Plugin> result = new ArrayList<>(src.size() + tgt.size());
267 for (Map.Entry<Object, Plugin> entry : master.entrySet()) {
268 List<Plugin> pre = predecessors.get(entry.getKey());
269 if (pre != null) {
270 result.addAll(pre);
271 }
272 result.add(entry.getValue());
273 }
274 result.addAll(pending);
275
276 builder.plugins(result);
277 }
278 }
279
280 @Override
281 protected Plugin mergePlugin(
282 Plugin target, Plugin source, boolean sourceDominant, Map<Object, Object> context) {
283 Plugin.Builder builder = Plugin.newBuilder(target);
284 if (source.isInherited()) {
285 mergeConfigurationContainer(builder, target, source, sourceDominant, context);
286 }
287 mergePlugin_GroupId(builder, target, source, sourceDominant, context);
288 mergePlugin_ArtifactId(builder, target, source, sourceDominant, context);
289 mergePlugin_Version(builder, target, source, sourceDominant, context);
290 mergePlugin_Extensions(builder, target, source, sourceDominant, context);
291 mergePlugin_Executions(builder, target, source, sourceDominant, context);
292 mergePlugin_Dependencies(builder, target, source, sourceDominant, context);
293 return builder.build();
294 }
295
296 @Override
297 protected void mergeReporting_Plugins(
298 Reporting.Builder builder,
299 Reporting target,
300 Reporting source,
301 boolean sourceDominant,
302 Map<Object, Object> context) {
303 List<ReportPlugin> src = source.getPlugins();
304 if (!src.isEmpty()) {
305 List<ReportPlugin> tgt = target.getPlugins();
306 Map<Object, ReportPlugin> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
307
308 for (ReportPlugin element : src) {
309 if (element.isInherited()) {
310
311 ReportPlugin plugin = ReportPlugin.newInstance(false);
312 plugin = mergeReportPlugin(plugin, element, sourceDominant, context);
313
314 merged.put(getReportPluginKey().apply(element), plugin);
315 }
316 }
317
318 for (ReportPlugin element : tgt) {
319 Object key = getReportPluginKey().apply(element);
320 ReportPlugin existing = merged.get(key);
321 if (existing != null) {
322 element = mergeReportPlugin(element, existing, sourceDominant, context);
323 }
324 merged.put(key, element);
325 }
326
327 builder.plugins(merged.values());
328 }
329 }
330 }
331 }