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 }