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.model.normalization;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.LinkedHashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.function.Function;
30  
31  import org.apache.maven.api.model.Build;
32  import org.apache.maven.api.model.Dependency;
33  import org.apache.maven.api.model.Model;
34  import org.apache.maven.api.model.Plugin;
35  import org.apache.maven.model.building.ModelBuildingRequest;
36  import org.apache.maven.model.building.ModelProblemCollector;
37  import org.apache.maven.model.merge.MavenModelMerger;
38  import org.codehaus.plexus.util.StringUtils;
39  
40  /**
41   * Handles normalization of a model.
42   *
43   * @author Benjamin Bentmann
44   */
45  @Named
46  @Singleton
47  public class DefaultModelNormalizer implements ModelNormalizer {
48  
49      private DuplicateMerger merger = new DuplicateMerger();
50  
51      @Override
52      public void mergeDuplicates(
53              org.apache.maven.model.Model model, ModelBuildingRequest request, ModelProblemCollector problems) {
54          model.update(mergeDuplicates(model.getDelegate(), request, problems));
55      }
56  
57      @Override
58      public void injectDefaultValues(
59              org.apache.maven.model.Model model, ModelBuildingRequest request, ModelProblemCollector problems) {
60          model.update(injectDefaultValues(model.getDelegate(), request, problems));
61      }
62  
63      @Override
64      public Model mergeDuplicates(Model model, ModelBuildingRequest request, ModelProblemCollector problems) {
65          Model.Builder builder = Model.newBuilder(model);
66  
67          Build build = model.getBuild();
68          if (build != null) {
69              List<Plugin> plugins = build.getPlugins();
70              Map<Object, Plugin> normalized = new LinkedHashMap<>(plugins.size() * 2);
71  
72              for (Plugin plugin : plugins) {
73                  Object key = plugin.getKey();
74                  Plugin first = normalized.get(key);
75                  if (first != null) {
76                      plugin = merger.mergePlugin(plugin, first);
77                  }
78                  normalized.put(key, plugin);
79              }
80  
81              if (plugins.size() != normalized.size()) {
82                  builder.build(
83                          Build.newBuilder(build).plugins(normalized.values()).build());
84              }
85          }
86  
87          /*
88           * NOTE: This is primarily to keep backward-compat with Maven 2.x which did not validate that dependencies are
89           * unique within a single POM. Upon multiple declarations, 2.x just kept the last one but retained the order of
90           * the first occurrence. So when we're in lenient/compat mode, we have to deal with such broken POMs and mimic
91           * the way 2.x works. When we're in strict mode, the removal of duplicates just saves other merging steps from
92           * aftereffects and bogus error messages.
93           */
94          List<Dependency> dependencies = model.getDependencies();
95          Map<String, Dependency> normalized = new LinkedHashMap<>(dependencies.size() * 2);
96  
97          for (Dependency dependency : dependencies) {
98              normalized.put(dependency.getManagementKey(), dependency);
99          }
100 
101         if (dependencies.size() != normalized.size()) {
102             builder.dependencies(normalized.values());
103         }
104 
105         return builder.build();
106     }
107 
108     /**
109      * DuplicateMerger
110      */
111     protected static class DuplicateMerger extends MavenModelMerger {
112 
113         public Plugin mergePlugin(Plugin target, Plugin source) {
114             return super.mergePlugin(target, source, false, Collections.emptyMap());
115         }
116     }
117 
118     @Override
119     public Model injectDefaultValues(Model model, ModelBuildingRequest request, ModelProblemCollector problems) {
120         Model.Builder builder = Model.newBuilder(model);
121 
122         builder.dependencies(injectList(model.getDependencies(), this::injectDependency));
123         Build build = model.getBuild();
124         if (build != null) {
125             Build newBuild = Build.newBuilder(build)
126                     .plugins(injectList(build.getPlugins(), this::injectPlugin))
127                     .build();
128             builder.build(newBuild != build ? newBuild : null);
129         }
130 
131         return builder.build();
132     }
133 
134     private Plugin injectPlugin(Plugin p) {
135         return Plugin.newBuilder(p)
136                 .dependencies(injectList(p.getDependencies(), this::injectDependency))
137                 .build();
138     }
139 
140     private Dependency injectDependency(Dependency d) {
141         // we cannot set this directly in the MDO due to the interactions with dependency management
142         return StringUtils.isEmpty(d.getScope()) ? d.withScope("compile") : d;
143     }
144 
145     /**
146      * Returns a list suited for the builders, i.e. null if not modified
147      */
148     private <T> List<T> injectList(List<T> list, Function<T, T> modifer) {
149         List<T> newList = null;
150         for (int i = 0; i < list.size(); i++) {
151             T oldT = list.get(i);
152             T newT = modifer.apply(oldT);
153             if (newT != oldT) {
154                 if (newList == null) {
155                     newList = new ArrayList<>(list);
156                 }
157                 newList.set(i, newT);
158             }
159         }
160         return newList;
161     }
162 }