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