001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019 */
020package org.sonar.api.batch.maven;
021
022import org.apache.commons.lang.StringUtils;
023import org.apache.commons.lang.builder.ToStringBuilder;
024import org.apache.maven.model.Plugin;
025import org.apache.maven.model.ReportPlugin;
026import org.apache.maven.project.MavenProject;
027import org.codehaus.plexus.util.xml.Xpp3Dom;
028
029import java.util.Collection;
030import java.util.Iterator;
031import java.util.List;
032
033/**
034 * A class to handle maven plugins
035 *
036 * @since 1.10
037 */
038public class MavenPlugin {
039
040  private Plugin plugin;
041  private Xpp3Dom configuration;
042
043  /**
044   * Creates a MavenPlugin based on a Plugin
045   *
046   * @param plugin the plugin
047   */
048  public MavenPlugin(Plugin plugin) {
049    this.plugin = plugin;
050    this.configuration = (Xpp3Dom) plugin.getConfiguration();
051    if (this.configuration == null) {
052      configuration = new Xpp3Dom("configuration");
053      plugin.setConfiguration(this.configuration);
054    }
055  }
056
057  /**
058   * Creates a Maven plugin based on artifact + group + version
059   *
060   * @param groupId the group id
061   * @param artifactId the artifact id
062   * @param version the version
063   */
064  public MavenPlugin(String groupId, String artifactId, String version) {
065    this.plugin = new Plugin();
066    plugin.setGroupId(groupId);
067    plugin.setArtifactId(artifactId);
068    plugin.setVersion(version);
069    configuration = new Xpp3Dom("configuration");
070    plugin.setConfiguration(this.configuration);
071  }
072
073  /**
074   * Sets the maven plugin version
075   *
076   * @param version the version
077   * @return this
078   */
079  public MavenPlugin setVersion(String version) {
080    this.plugin.setVersion(version);
081    return this;
082  }
083
084  /**
085   * @return the underlying plugin
086   */
087  public Plugin getPlugin() {
088    return plugin;
089  }
090
091  /**
092   * Gets a parameter of the plugin based on its key
093   *
094   * @param key the param key
095   * @return the parameter if exist, null otherwise
096   */
097  public String getParameter(String key) {
098    Xpp3Dom node = findNodeWith(key);
099    return node == null ? null : node.getValue();
100  }
101
102  /**
103   * Gets a list of parameters of the plugin from a param key
104   *
105   * @param key param key with option-index snippet: e.g. item[0], item[1]. If no index snippet is passed, then
106   *            0 is default (index <=> index[0])
107   * @return an array of parameters if any, an empty array otherwise
108   */
109  public String[] getParameters(String key) {
110    String[] keyParts = StringUtils.split(key, "/");
111    Xpp3Dom node = configuration;
112    for (int i = 0; i < keyParts.length - 1; i++) {
113      node = getOrCreateChild(node, keyParts[i]);
114    }
115    Xpp3Dom[] children = node.getChildren(keyParts[keyParts.length - 1]);
116    String[] result = new String[children.length];
117    for (int i = 0; i < children.length; i++) {
118      result[i] = children[i].getValue();
119    }
120    return result;
121  }
122
123  /**
124   * Sets a parameter for the maven plugin. This will overrides an existing parameter.
125   *
126   * @param key the param key
127   * @param value the param value
128   * @return this
129   */
130  public MavenPlugin setParameter(String key, String value) {
131    checkKeyArgument(key);
132    String[] keyParts = StringUtils.split(key, "/");
133    Xpp3Dom node = configuration;
134    for (String keyPart : keyParts) {
135      node = getOrCreateChild(node, keyPart);
136    }
137    node.setValue(value);
138    return this;
139  }
140
141  /**
142   * Sets a parameter to the maven plugin. Overrides existing parameter only id specified.
143   *
144   * @param key the param key
145   * @param value the param value
146   * @param override whether to override existing parameter
147   */
148  public void setParameter(String key, String value, boolean override) {
149    if (getParameter(key) == null || override) {
150      setParameter(key, value);
151    }
152  }
153
154  /**
155   * Removes all parameters from the maven plugin
156   */
157  public void removeParameters() {
158    configuration = new Xpp3Dom("configuration");
159    plugin.setConfiguration(this.configuration);
160  }
161
162  /**
163   * Adds a parameter to the maven plugin
164   *
165   * @param key the param key with option-index snippet: e.g. item[0], item[1]. If no index snippet is passed, then
166   *            0 is default (index <=> index[0])
167   * @param value the param value
168   * @return this
169   */
170  public MavenPlugin addParameter(String key, String value) {
171    String[] keyParts = StringUtils.split(key, "/");
172    Xpp3Dom node = configuration;
173    for (int i = 0; i < keyParts.length - 1; i++) {
174      node = getOrCreateChild(node, keyParts[i]);
175    }
176    Xpp3Dom leaf = new Xpp3Dom(keyParts[keyParts.length - 1]);
177    leaf.setValue(value);
178    node.addChild(leaf);
179    return this;
180  }
181
182  private static Xpp3Dom getOrCreateChild(Xpp3Dom node, String key) {
183    int childIndex = getIndex(key);
184
185    if (node.getChildren(removeIndexSnippet(key)).length <= childIndex) {
186      Xpp3Dom child = new Xpp3Dom(removeIndexSnippet(key));
187      node.addChild(child);
188      return child;
189    }
190    return node.getChildren(removeIndexSnippet(key))[childIndex];
191
192  }
193
194  private static int getIndex(String key) {
195    //parsing index-syntax (e.g. item[1])
196    if (key.matches(".*?\\[\\d+\\]")) {
197      return Integer.parseInt(StringUtils.substringBetween(key, "[", "]"));
198    }
199    // for down-compatibility of api we fallback to default 0
200    return 0;
201  }
202
203  private static String removeIndexSnippet(String key) {
204    return StringUtils.substringBefore(key, "[");
205  }
206
207  /**
208   * Remove a parameter from the maven plugin based on its key
209   *
210   * @param key param key with option-index snippet: e.g. item[0], item[1]. If no index snippet is passed, then
211   *            0 is default (index <=> index[0])
212   */
213  public void removeParameter(String key) {
214    Xpp3Dom node = findNodeWith(key);
215    if (node != null) {
216      remove(node);
217    }
218  }
219
220  private Xpp3Dom findNodeWith(String key) {
221    checkKeyArgument(key);
222    String[] keyParts = key.split("/");
223    Xpp3Dom node = configuration;
224    for (String keyPart : keyParts) {
225
226      if(node.getChildren(removeIndexSnippet(keyPart)).length <= getIndex(keyPart)) {
227        return null;
228      }
229
230      node = node.getChildren(removeIndexSnippet(keyPart))[getIndex(keyPart)];
231      if (node == null) {
232        return null;
233      }
234    }
235    return node;
236  }
237
238  private static void remove(Xpp3Dom node) {
239    Xpp3Dom parent = node.getParent();
240    for (int i = 0; i < parent.getChildCount(); i++) {
241      Xpp3Dom child = parent.getChild(i);
242      if (child.equals(node)) {
243        parent.removeChild(i);
244        break;
245      }
246    }
247  }
248
249  /**
250   * @return whether the maven plugin has got configuration
251   */
252  public boolean hasConfiguration() {
253    return configuration.getChildCount()>0;
254  }
255
256  private static void checkKeyArgument(String key) {
257    if (key == null) {
258      throw new IllegalArgumentException("Parameter 'key' should not be null.");
259    }
260  }
261
262  /**
263   * Registers a plugin in a project pom
264   * <p/>
265   * <p>Adds the plugin if it does not exist or amend its version if it does exist and specified</p>
266   *
267   * @param pom the project pom
268   * @param groupId the plugin group id
269   * @param artifactId the plugin artifact id
270   * @param version the plugin version
271   * @param overrideVersion whether to override the version if the plugin is already registered
272   * @return the registered plugin
273   */
274  public static MavenPlugin registerPlugin(MavenProject pom, String groupId, String artifactId, String version, boolean overrideVersion) {
275    MavenPlugin plugin = getPlugin(pom, groupId, artifactId);
276    if (plugin == null) {
277      plugin = new MavenPlugin(groupId, artifactId, version);
278
279    } else if (overrideVersion) {
280      plugin.setVersion(version);
281    }
282
283    // remove from pom
284    unregisterPlugin(pom, groupId, artifactId);
285
286    // register
287    pom.getBuild().addPlugin(plugin.getPlugin());
288
289    return plugin;
290  }
291
292  /**
293   * Returns a plugin from a pom based on its group id and artifact id
294   * <p/>
295   * <p>It searches in the build section, then the reporting section and finally the pluginManagement section</p>
296   *
297   * @param pom the project pom
298   * @param groupId the plugin group id
299   * @param artifactId the plugin artifact id
300   * @return the plugin if it exists, null otherwise
301   */
302  public static MavenPlugin getPlugin(MavenProject pom, String groupId, String artifactId) {
303    if (pom == null) {
304      return null;
305    }
306    // look for plugin in <build> section
307    Plugin plugin = null;
308    if (pom.getBuildPlugins() != null) {
309      plugin = getPlugin(pom.getBuildPlugins(), groupId, artifactId);
310    }
311
312    // look for plugin in <report> section
313    if (plugin == null && pom.getReportPlugins() != null) {
314      plugin = getReportPlugin(pom.getReportPlugins(), groupId, artifactId);
315    }
316
317    // look for plugin in <pluginManagement> section
318    if (pom.getPluginManagement() != null) {
319      Plugin pluginManagement = getPlugin(pom.getPluginManagement().getPlugins(), groupId, artifactId);
320      if (plugin == null) {
321        plugin = pluginManagement;
322
323      } else if (pluginManagement != null) {
324        if (pluginManagement.getConfiguration() != null) {
325          if (plugin.getConfiguration() == null) {
326            plugin.setConfiguration(pluginManagement.getConfiguration());
327          } else {
328            Xpp3Dom.mergeXpp3Dom((Xpp3Dom) plugin.getConfiguration(), (Xpp3Dom) pluginManagement.getConfiguration());
329          }
330        }
331        if (plugin.getDependencies() == null && pluginManagement.getDependencies() != null) {
332          plugin.setDependencies(pluginManagement.getDependencies());
333        }
334        if (plugin.getVersion() == null) {
335          plugin.setVersion(pluginManagement.getVersion());
336        }
337      }
338    }
339
340    if (plugin != null) {
341      return new MavenPlugin(plugin);
342    }
343    return null;
344  }
345
346  private static Plugin getPlugin(Collection<Plugin> plugins, String groupId, String artifactId) {
347    if (plugins == null) {
348      return null;
349    }
350
351    for (Plugin plugin : plugins) {
352      if (MavenUtils.equals(plugin, groupId, artifactId)) {
353        return plugin;
354      }
355    }
356    return null;
357  }
358
359  private static Plugin getReportPlugin(Collection<ReportPlugin> plugins, String groupId, String artifactId) {
360    if (plugins == null) {
361      return null;
362    }
363
364    for (ReportPlugin plugin : plugins) {
365      if (MavenUtils.equals(plugin, groupId, artifactId)) {
366        return cloneReportPluginToPlugin(plugin);
367      }
368    }
369    return null;
370  }
371
372  private static Plugin cloneReportPluginToPlugin(ReportPlugin reportPlugin) {
373    Plugin plugin = new Plugin();
374    plugin.setGroupId(reportPlugin.getGroupId());
375    plugin.setArtifactId(reportPlugin.getArtifactId());
376    plugin.setVersion(reportPlugin.getVersion());
377    plugin.setConfiguration(reportPlugin.getConfiguration());
378    return plugin;
379  }
380
381  private static void unregisterPlugin(MavenProject pom, String groupId, String artifactId) {
382    if (pom.getPluginManagement() != null && pom.getPluginManagement().getPlugins() != null) {
383      unregisterPlugin(pom.getPluginManagement().getPlugins(), groupId, artifactId);
384    }
385    if (pom.getBuildPlugins() != null && pom.getBuildPlugins() != null) {
386      unregisterPlugin(pom.getBuildPlugins(), groupId, artifactId);
387    }
388    if (pom.getReportPlugins() != null) {
389      unregisterReportPlugin(pom.getReportPlugins(), groupId, artifactId);
390    }
391  }
392
393  private static void unregisterPlugin(List<Plugin> plugins, String groupId, String artifactId) {
394    for (Iterator<Plugin> iterator = plugins.iterator(); iterator.hasNext();) {
395      Plugin p = iterator.next();
396      if (MavenUtils.equals(p, groupId, artifactId)) {
397        iterator.remove();
398      }
399    }
400  }
401
402  private static void unregisterReportPlugin(List<ReportPlugin> plugins, String groupId, String artifactId) {
403    for (Iterator<ReportPlugin> iterator = plugins.iterator(); iterator.hasNext();) {
404      ReportPlugin p = iterator.next();
405      if (MavenUtils.equals(p, groupId, artifactId)) {
406        iterator.remove();
407      }
408    }
409  }
410
411
412  @Override
413  public String toString() {
414    return new ToStringBuilder(this)
415        .append("groupId", plugin.getGroupId())
416        .append("artifactId", plugin.getArtifactId())
417        .append("version", plugin.getVersion())
418        .toString();
419  }
420}