View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.internal.impl.model;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.LinkedHashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.WeakHashMap;
27  import java.util.concurrent.ConcurrentHashMap;
28  
29  import org.apache.maven.api.di.Named;
30  import org.apache.maven.api.di.Singleton;
31  import org.apache.maven.api.model.Build;
32  import org.apache.maven.api.model.BuildBase;
33  import org.apache.maven.api.model.Model;
34  import org.apache.maven.api.model.ModelBase;
35  import org.apache.maven.api.model.Plugin;
36  import org.apache.maven.api.model.PluginContainer;
37  import org.apache.maven.api.model.PluginExecution;
38  import org.apache.maven.api.model.Profile;
39  import org.apache.maven.api.model.ReportPlugin;
40  import org.apache.maven.api.model.ReportSet;
41  import org.apache.maven.api.model.Reporting;
42  import org.apache.maven.api.services.ModelBuilderRequest;
43  import org.apache.maven.api.services.ModelProblemCollector;
44  import org.apache.maven.api.services.model.ProfileInjector;
45  
46  /**
47   * Handles profile injection into the model.
48   *
49   */
50  @Named
51  @Singleton
52  public class DefaultProfileInjector implements ProfileInjector {
53  
54      private static final Map<Model, Map<List<Profile>, Model>> CACHE = Collections.synchronizedMap(new WeakHashMap<>());
55  
56      // In order for the weak hash map to work correctly, we must not hold any reference to
57      // the model used as the key.  So we use a dummy model as a placeholder to indicate that
58      // we want to store the model used as they key.
59      private static final Model KEY = Model.newInstance();
60  
61      private final ProfileModelMerger merger = new ProfileModelMerger();
62  
63      @Override
64      public Model injectProfiles(
65              Model model, List<Profile> profiles, ModelBuilderRequest request, ModelProblemCollector problems) {
66          Model result = CACHE.computeIfAbsent(model, k -> new ConcurrentHashMap<>())
67                  .computeIfAbsent(profiles, l -> doInjectProfiles(model, profiles));
68          return result == KEY ? model : result;
69      }
70  
71      private Model doInjectProfiles(Model model, List<Profile> profiles) {
72          Model orgModel = model;
73          for (Profile profile : profiles) {
74              if (profile != null) {
75                  Model.Builder builder = Model.newBuilder(model);
76                  merger.mergeModelBase(builder, model, profile);
77  
78                  if (profile.getBuild() != null) {
79                      Build build = model.getBuild() != null ? model.getBuild() : Build.newInstance();
80                      Build.Builder bbuilder = Build.newBuilder(build);
81                      merger.mergeBuildBase(bbuilder, build, profile.getBuild());
82                      builder.build(bbuilder.build());
83                  }
84  
85                  model = builder.build();
86              }
87          }
88          return model == orgModel ? KEY : model;
89      }
90  
91      /**
92       * ProfileModelMerger
93       */
94      protected static class ProfileModelMerger extends MavenModelMerger {
95  
96          public void mergeModelBase(ModelBase.Builder builder, ModelBase target, ModelBase source) {
97              mergeModelBase(builder, target, source, true, Collections.emptyMap());
98          }
99  
100         public void mergeBuildBase(BuildBase.Builder builder, BuildBase target, BuildBase source) {
101             mergeBuildBase(builder, target, source, true, Collections.emptyMap());
102         }
103 
104         @Override
105         protected void mergePluginContainer_Plugins(
106                 PluginContainer.Builder builder,
107                 PluginContainer target,
108                 PluginContainer source,
109                 boolean sourceDominant,
110                 Map<Object, Object> context) {
111             List<Plugin> src = source.getPlugins();
112             if (!src.isEmpty()) {
113                 List<Plugin> tgt = target.getPlugins();
114                 Map<Object, Plugin> master = new LinkedHashMap<>(tgt.size() * 2);
115 
116                 for (Plugin element : tgt) {
117                     Object key = getPluginKey().apply(element);
118                     master.put(key, element);
119                 }
120 
121                 Map<Object, List<Plugin>> predecessors = new LinkedHashMap<>();
122                 List<Plugin> pending = new ArrayList<>();
123                 for (Plugin element : src) {
124                     Object key = getPluginKey().apply(element);
125                     Plugin existing = master.get(key);
126                     if (existing != null) {
127                         existing = mergePlugin(existing, element, sourceDominant, context);
128                         master.put(key, existing);
129                         if (!pending.isEmpty()) {
130                             predecessors.put(key, pending);
131                             pending = new ArrayList<>();
132                         }
133                     } else {
134                         pending.add(element);
135                     }
136                 }
137 
138                 List<Plugin> result = new ArrayList<>(src.size() + tgt.size());
139                 for (Map.Entry<Object, Plugin> entry : master.entrySet()) {
140                     List<Plugin> pre = predecessors.get(entry.getKey());
141                     if (pre != null) {
142                         result.addAll(pre);
143                     }
144                     result.add(entry.getValue());
145                 }
146                 result.addAll(pending);
147 
148                 builder.plugins(result);
149             }
150         }
151 
152         @Override
153         protected void mergePlugin_Executions(
154                 Plugin.Builder builder,
155                 Plugin target,
156                 Plugin source,
157                 boolean sourceDominant,
158                 Map<Object, Object> context) {
159             List<PluginExecution> src = source.getExecutions();
160             if (!src.isEmpty()) {
161                 List<PluginExecution> tgt = target.getExecutions();
162                 Map<Object, PluginExecution> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
163 
164                 for (PluginExecution element : tgt) {
165                     Object key = getPluginExecutionKey().apply(element);
166                     merged.put(key, element);
167                 }
168 
169                 for (PluginExecution element : src) {
170                     Object key = getPluginExecutionKey().apply(element);
171                     PluginExecution existing = merged.get(key);
172                     if (existing != null) {
173                         element = mergePluginExecution(existing, element, sourceDominant, context);
174                     }
175                     merged.put(key, element);
176                 }
177 
178                 builder.executions(merged.values());
179             }
180         }
181 
182         @Override
183         protected void mergeReporting_Plugins(
184                 Reporting.Builder builder,
185                 Reporting target,
186                 Reporting source,
187                 boolean sourceDominant,
188                 Map<Object, Object> context) {
189             List<ReportPlugin> src = source.getPlugins();
190             if (!src.isEmpty()) {
191                 List<ReportPlugin> tgt = target.getPlugins();
192                 Map<Object, ReportPlugin> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
193 
194                 for (ReportPlugin element : tgt) {
195                     Object key = getReportPluginKey().apply(element);
196                     merged.put(key, element);
197                 }
198 
199                 for (ReportPlugin element : src) {
200                     Object key = getReportPluginKey().apply(element);
201                     ReportPlugin existing = merged.get(key);
202                     if (existing != null) {
203                         element = mergeReportPlugin(existing, element, sourceDominant, context);
204                     }
205                     merged.put(key, element);
206                 }
207 
208                 builder.plugins(merged.values());
209             }
210         }
211 
212         @Override
213         protected void mergeReportPlugin_ReportSets(
214                 ReportPlugin.Builder builder,
215                 ReportPlugin target,
216                 ReportPlugin source,
217                 boolean sourceDominant,
218                 Map<Object, Object> context) {
219             List<ReportSet> src = source.getReportSets();
220             if (!src.isEmpty()) {
221                 List<ReportSet> tgt = target.getReportSets();
222                 Map<Object, ReportSet> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
223 
224                 for (ReportSet element : tgt) {
225                     Object key = getReportSetKey().apply(element);
226                     merged.put(key, element);
227                 }
228 
229                 for (ReportSet element : src) {
230                     Object key = getReportSetKey().apply(element);
231                     ReportSet existing = merged.get(key);
232                     if (existing != null) {
233                         element = mergeReportSet(existing, element, sourceDominant, context);
234                     }
235                     merged.put(key, element);
236                 }
237 
238                 builder.reportSets(merged.values());
239             }
240         }
241     }
242 }