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 }