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.resolver.internal.ant.types;
20  
21  import java.util.regex.Matcher;
22  import java.util.regex.Pattern;
23  
24  import org.apache.tools.ant.BuildException;
25  import org.apache.tools.ant.Task;
26  import org.apache.tools.ant.types.DataType;
27  import org.apache.tools.ant.types.Reference;
28  
29  /**
30   * Represents a Maven dependency exclusion in an Ant build script.
31   * <p>
32   * This type is used within a {@link org.apache.maven.resolver.internal.ant.types.Dependency}
33   * to specify transitive dependencies that should be excluded when the parent dependency is resolved.
34   * Exclusions help avoid unwanted or conflicting artifacts that would otherwise be pulled in transitively.
35   * </p>
36   *
37   * <h2>Usage Example:</h2>
38   * <pre>{@code
39   * <dependency groupId="org.example" artifactId="example-lib" version="1.0.0">
40   *   <exclusion groupId="commons-logging" artifactId="commons-logging"/>
41   * </dependency>
42   * }</pre>
43   *
44   * <h2>Attributes:</h2>
45   * <ul>
46   *   <li><strong>groupId</strong> — the group ID of the dependency to exclude (required)</li>
47   *   <li><strong>artifactId</strong> — the artifact ID of the dependency to exclude (required)</li>
48   * </ul>
49   *
50   * <h2>Typical Use Cases:</h2>
51   * <ul>
52   *   <li>Preventing known problematic or unwanted transitive dependencies</li>
53   *   <li>Ensuring consistency by excluding duplicates or old versions</li>
54   *   <li>Controlling the effective dependency graph in complex builds</li>
55   * </ul>
56   *
57   * <p>
58   * The exclusion applies only to the specific dependency in which it is declared,
59   * and does not affect other dependencies that may bring in the same artifact transitively.
60   * </p>
61   *
62   * @see org.apache.maven.resolver.internal.ant.types.Dependency
63   */
64  public class Exclusion extends DataType {
65  
66      private static final String WILDCARD = "*";
67  
68      private String groupId;
69  
70      private String artifactId;
71  
72      private String classifier;
73  
74      private String extension;
75  
76      /**
77       * Default constructor for Exclusion.
78       * <p>
79       * This constructor is used when the Exclusion is created as a nested element in an Ant task.
80       * </p>
81       */
82      public Exclusion() {
83          // Default constructor
84      }
85  
86      /**
87       * Resolves this object if defined as a reference and verifies that it is a
88       * {@code Exclusion} instance.
89       *
90       * @return the referenced {@code Exclusion} instance
91       * @throws org.apache.tools.ant.BuildException if the reference is invalid
92       *
93       * @see #setRefid(org.apache.tools.ant.types.Reference)
94       * @see #isReference()
95       */
96      protected Exclusion getRef() {
97          return getCheckedRef(Exclusion.class);
98      }
99  
100     /**
101      * Validates that the required attributes of this exclusion are set.
102      * <p>
103      * Specifically, both {@code groupId} and {@code artifactId} must be provided
104      * for the exclusion to be valid. If either is missing, this method throws
105      * a {@link org.apache.tools.ant.BuildException}, indicating a configuration error
106      * in the Ant build script.
107      * </p>
108      *
109      * <p>
110      * This method is typically called during task execution to ensure that the
111      * exclusion is well-formed before it is passed to the underlying Maven model.
112      * </p>
113      *
114      * @param task the Ant task context used for error reporting
115      *
116      * @throws org.apache.tools.ant.BuildException if {@code groupId} or {@code artifactId} is not set
117      *
118      * @see #setGroupId(String)
119      * @see #setArtifactId(String)
120      */
121     public void validate(Task task) {
122         if (isReference()) {
123             getRef().validate(task);
124         } else {
125             if (groupId == null && artifactId == null && classifier == null && extension == null) {
126                 throw new BuildException(
127                         "You must specify at least one of " + "'groupId', 'artifactId', 'classifier' or 'extension'");
128             }
129         }
130     }
131 
132     @Override
133     public void setRefid(Reference ref) {
134         if (groupId != null || artifactId != null || extension != null || classifier != null) {
135             throw tooManyAttributes();
136         }
137         super.setRefid(ref);
138     }
139 
140     /**
141      * Returns the {@code groupId} of the dependency to exclude.
142      * <p>
143      * This is a required attribute that identifies the group (organization or project)
144      * of the transitive dependency to be excluded. The exclusion will only be valid
145      * if both {@code groupId} and {@code artifactId} are specified.
146      * </p>
147      *
148      * @return the group ID of the excluded dependency, or {@code null} if not set
149      *
150      * @see #setGroupId(String)
151      * @see #getArtifactId()
152      * @see #validate(org.apache.tools.ant.Task)
153      */
154     public String getGroupId() {
155         if (isReference()) {
156             return getRef().getGroupId();
157         }
158         return (groupId != null) ? groupId : WILDCARD;
159     }
160 
161     /**
162      * Sets the {@code groupId} of the dependency to exclude.
163      * <p>
164      * The {@code groupId} identifies the group or organization that provides the
165      * transitive dependency you want to exclude. This is a required attribute
166      * for a valid exclusion.
167      * </p>
168      *
169      * <p>
170      * The value may include Ant properties (e.g., {@code ${excluded.groupId}}),
171      * which will be resolved at runtime using the current Ant project context.
172      * </p>
173      *
174      * @param groupId the group ID of the excluded dependency
175      *
176      * @see #getGroupId()
177      * @see #setArtifactId(String)
178      * @see #validate(org.apache.tools.ant.Task)
179      */
180     public void setGroupId(String groupId) {
181         checkAttributesAllowed();
182         if (this.groupId != null) {
183             throw ambiguousCoords();
184         }
185         this.groupId = groupId;
186     }
187 
188     /**
189      * Returns the {@code artifactId} of the dependency to exclude.
190      * <p>
191      * This is a required attribute that specifies the artifact within the given {@code groupId}
192      * that should be excluded from transitive dependency resolution.
193      * </p>
194      *
195      * @return the artifact ID of the excluded dependency, or {@code null} if not set
196      *
197      * @see #setArtifactId(String)
198      * @see #getGroupId()
199      * @see #validate(org.apache.tools.ant.Task)
200      */
201     public String getArtifactId() {
202         if (isReference()) {
203             return getRef().getArtifactId();
204         }
205         return (artifactId != null) ? artifactId : WILDCARD;
206     }
207 
208     /**
209      * Sets the {@code artifactId} of the dependency to exclude.
210      * <p>
211      * The {@code artifactId} identifies the specific artifact (e.g., library or module name)
212      * to exclude from the transitive dependency graph. This is a required attribute and must
213      * be used in conjunction with {@code groupId}.
214      * </p>
215      *
216      * <p>
217      * The value may include Ant properties (e.g., {@code ${excluded.artifactId}}),
218      * which will be resolved at runtime.
219      * </p>
220      *
221      * @param artifactId the artifact ID of the excluded dependency
222      *
223      * @see #getArtifactId()
224      * @see #setGroupId(String)
225      * @see #validate(org.apache.tools.ant.Task)
226      */
227     public void setArtifactId(String artifactId) {
228         checkAttributesAllowed();
229         if (this.artifactId != null) {
230             throw ambiguousCoords();
231         }
232         this.artifactId = artifactId;
233     }
234 
235     /**
236      * Returns the classifier of the dependency to exclude, if specified.
237      * <p>
238      * The classifier distinguishes artifacts that are built from the same
239      * source but differ in content, such as {@code sources}, {@code javadoc},
240      * or platform-specific variants.
241      * </p>
242      *
243      * <p>
244      * If no classifier is specified, this method returns {@code null}, and the exclusion
245      * applies to all classifiers of the specified {@code groupId:artifactId}.
246      * </p>
247      *
248      * @return the classifier of the excluded dependency, or {@code null} if not set
249      *
250      * @see #setClassifier(String)
251      */
252     public String getClassifier() {
253         if (isReference()) {
254             return getRef().getClassifier();
255         }
256         return (classifier != null) ? classifier : WILDCARD;
257     }
258 
259     /**
260      * Sets the classifier of the dependency to exclude.
261      * <p>
262      * The classifier distinguishes a variant of the artifact with the same {@code groupId},
263      * {@code artifactId}, and {@code type}, such as {@code sources} or {@code javadoc}.
264      * This is an optional attribute; if omitted, the exclusion will match all classifiers.
265      * </p>
266      *
267      * <p>
268      * The value may include Ant properties (e.g., {@code ${excluded.classifier}}),
269      * which will be resolved at runtime using the Ant project context.
270      * </p>
271      *
272      * @param classifier the classifier to match for exclusion
273      *
274      * @see #getClassifier()
275      */
276     public void setClassifier(String classifier) {
277         checkAttributesAllowed();
278         if (this.classifier != null) {
279             throw ambiguousCoords();
280         }
281         this.classifier = classifier;
282     }
283 
284     /**
285      * Returns the extension (file type) of the dependency to exclude.
286      * <p>
287      * If this exclusion is a reference to another one (via {@code refid}),
288      * the extension will be delegated to the referenced exclusion.
289      * </p>
290      *
291      * <p>
292      * If no extension has been explicitly set, this method returns a wildcard value
293      * (usually {@code "*"}), indicating that the exclusion applies to all extensions
294      * for the given coordinates.
295      * </p>
296      *
297      * @return the extension to match for exclusion, or a wildcard if not explicitly set
298      *
299      * @see #setExtension(String)
300      * @see #isReference()
301      */
302     public String getExtension() {
303         if (isReference()) {
304             return getRef().getExtension();
305         }
306         return (extension != null) ? extension : WILDCARD;
307     }
308 
309     /**
310      * Sets the extension (file type) of the dependency to exclude.
311      * <p>
312      * The extension typically corresponds to the artifact's file type,
313      * such as {@code jar}, {@code pom}, {@code zip}, etc.
314      * If not set, a wildcard value will be used to match any extension.
315      * </p>
316      *
317      * <p>
318      * This method may only be called if the exclusion is not defined as a reference
319      * (i.e., {@code refid} is not set). If this exclusion is a reference,
320      * or if the extension has already been set, this method will throw an exception.
321      * </p>
322      *
323      * @param extension the file extension to exclude
324      *
325      * @throws org.apache.tools.ant.BuildException if this exclusion is a reference or the extension is already set
326      *
327      * @see #getExtension()
328      * @see #isReference()
329      */
330     public void setExtension(String extension) {
331         checkAttributesAllowed();
332         if (this.extension != null) {
333             throw ambiguousCoords();
334         }
335         this.extension = extension;
336     }
337 
338     /**
339      * Sets the exclusion coordinates using a compact colon-separated format.
340      * <p>
341      * This is a convenience method that allows you to specify the exclusion
342      * as a single string in the format:
343      * </p>
344      * <pre>
345      * groupId[:artifactId[:extension[:classifier]]]
346      * </pre>
347      *
348      * <p>
349      * Any omitted segments are automatically set to {@code "*"} (wildcard),
350      * which matches all values for that component. For example:
351      * </p>
352      * <ul>
353      *   <li>{@code com.example} → excludes all artifacts from {@code com.example}</li>
354      *   <li>{@code com.example:my-lib} → excludes all extensions/classifiers of {@code my-lib}</li>
355      *   <li>{@code com.example:my-lib:jar:sources} → excludes only {@code my-lib-*.jar:sources}</li>
356      * </ul>
357      *
358      * <p>
359      * This method may only be used if no individual attributes (such as {@code groupId},
360      * {@code artifactId}, {@code extension}, or {@code classifier}) have been set,
361      * and the exclusion is not a reference (i.e., {@code refid} is not set).
362      * </p>
363      *
364      * @param coords the exclusion coordinates in colon-separated format
365      *
366      * @throws org.apache.tools.ant.BuildException if the exclusion is a reference,
367      *         if any individual coordinate attributes are already set, or if the format is invalid
368      *
369      * @see #setGroupId(String)
370      * @see #setArtifactId(String)
371      * @see #setExtension(String)
372      * @see #setClassifier(String)
373      * @see #isReference()
374      */
375     public void setCoords(String coords) {
376         checkAttributesAllowed();
377         if (groupId != null || artifactId != null || extension != null || classifier != null) {
378             throw ambiguousCoords();
379         }
380         Pattern p = Pattern.compile("([^: ]+)(:([^: ]+)(:([^: ]+)(:([^: ]*))?)?)?");
381         Matcher m = p.matcher(coords);
382         if (!m.matches()) {
383             throw new BuildException("Bad exclusion coordinates '" + coords
384                     + "', expected format is <groupId>[:<artifactId>[:<extension>[:<classifier>]]]");
385         }
386         groupId = m.group(1);
387         artifactId = m.group(3);
388         if (artifactId == null) {
389             artifactId = "*";
390         }
391         extension = m.group(5);
392         if (extension == null) {
393             extension = "*";
394         }
395         classifier = m.group(7);
396         if (classifier == null) {
397             classifier = "*";
398         }
399     }
400 
401     private BuildException ambiguousCoords() {
402         return new BuildException(
403                 "You must not specify both 'coords' and " + "('groupId', 'artifactId', 'extension', 'classifier')");
404     }
405 }