1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.internal.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 mergeModelBase_Properties(
199 ModelBase.Builder builder,
200 ModelBase target,
201 ModelBase source,
202 boolean sourceDominant,
203 Map<Object, Object> context) {
204 Map<String, String> merged = new HashMap<>();
205 if (sourceDominant) {
206 merged.putAll(target.getProperties());
207 putAll(merged, source.getProperties(), CHILD_DIRECTORY_PROPERTY);
208 } else {
209 putAll(merged, source.getProperties(), CHILD_DIRECTORY_PROPERTY);
210 merged.putAll(target.getProperties());
211 }
212 builder.properties(merged);
213 builder.location(
214 "properties",
215 InputLocation.merge(
216 target.getLocation("properties"), source.getLocation("properties"), sourceDominant));
217 }
218
219 private void putAll(Map<String, String> s, Map<String, String> t, Object excludeKey) {
220 for (Map.Entry<String, String> e : t.entrySet()) {
221 if (!e.getKey().equals(excludeKey)) {
222 s.put(e.getKey(), e.getValue());
223 }
224 }
225 }
226
227 @Override
228 protected void mergePluginContainer_Plugins(
229 PluginContainer.Builder builder,
230 PluginContainer target,
231 PluginContainer source,
232 boolean sourceDominant,
233 Map<Object, Object> context) {
234 List<Plugin> src = source.getPlugins();
235 if (!src.isEmpty()) {
236 List<Plugin> tgt = target.getPlugins();
237 Map<Object, Plugin> master = new LinkedHashMap<>(src.size() * 2);
238
239 for (Plugin element : src) {
240 if (element.isInherited() || !element.getExecutions().isEmpty()) {
241
242 Plugin plugin = Plugin.newInstance(false);
243 plugin = mergePlugin(plugin, element, sourceDominant, context);
244
245 Object key = getPluginKey().apply(plugin);
246
247 master.put(key, plugin);
248 }
249 }
250
251 Map<Object, List<Plugin>> predecessors = new LinkedHashMap<>();
252 List<Plugin> pending = new ArrayList<>();
253 for (Plugin element : tgt) {
254 Object key = getPluginKey().apply(element);
255 Plugin existing = master.get(key);
256 if (existing != null) {
257 element = mergePlugin(element, existing, sourceDominant, context);
258
259 master.put(key, element);
260
261 if (!pending.isEmpty()) {
262 predecessors.put(key, pending);
263 pending = new ArrayList<>();
264 }
265 } else {
266 pending.add(element);
267 }
268 }
269
270 List<Plugin> result = new ArrayList<>(src.size() + tgt.size());
271 for (Map.Entry<Object, Plugin> entry : master.entrySet()) {
272 List<Plugin> pre = predecessors.get(entry.getKey());
273 if (pre != null) {
274 result.addAll(pre);
275 }
276 result.add(entry.getValue());
277 }
278 result.addAll(pending);
279
280 builder.plugins(result);
281 }
282 }
283
284 @Override
285 protected Plugin mergePlugin(
286 Plugin target, Plugin source, boolean sourceDominant, Map<Object, Object> context) {
287 Plugin.Builder builder = Plugin.newBuilder(target);
288 if (source.isInherited()) {
289 mergeConfigurationContainer(builder, target, source, sourceDominant, context);
290 }
291 mergePlugin_GroupId(builder, target, source, sourceDominant, context);
292 mergePlugin_ArtifactId(builder, target, source, sourceDominant, context);
293 mergePlugin_Version(builder, target, source, sourceDominant, context);
294 mergePlugin_Extensions(builder, target, source, sourceDominant, context);
295 mergePlugin_Executions(builder, target, source, sourceDominant, context);
296 mergePlugin_Dependencies(builder, target, source, sourceDominant, context);
297 return builder.build();
298 }
299
300 @Override
301 protected void mergeReporting_Plugins(
302 Reporting.Builder builder,
303 Reporting target,
304 Reporting source,
305 boolean sourceDominant,
306 Map<Object, Object> context) {
307 List<ReportPlugin> src = source.getPlugins();
308 if (!src.isEmpty()) {
309 List<ReportPlugin> tgt = target.getPlugins();
310 Map<Object, ReportPlugin> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
311
312 for (ReportPlugin element : src) {
313 if (element.isInherited()) {
314
315 ReportPlugin plugin = ReportPlugin.newInstance(false);
316 plugin = mergeReportPlugin(plugin, element, sourceDominant, context);
317
318 merged.put(getReportPluginKey().apply(element), plugin);
319 }
320 }
321
322 for (ReportPlugin element : tgt) {
323 Object key = getReportPluginKey().apply(element);
324 ReportPlugin existing = merged.get(key);
325 if (existing != null) {
326 element = mergeReportPlugin(element, existing, sourceDominant, context);
327 }
328 merged.put(key, element);
329 }
330
331 builder.plugins(merged.values());
332 }
333 }
334 }
335 }