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