001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.artifact;
020
021import java.io.File;
022import java.nio.file.Path;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.Map;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028
029/**
030 * A simple artifact. <em>Note:</em> Instances of this class are immutable and the exposed mutators return new objects
031 * rather than changing the current instance.
032 */
033public final class DefaultArtifact extends AbstractArtifact {
034    private static final Pattern COORDINATE_PATTERN =
035            Pattern.compile("([^: ]+):([^: ]+)(:([^: ]*)(:([^: ]+))?)?:([^: ]+)");
036
037    private final String groupId;
038
039    private final String artifactId;
040
041    private final String version;
042
043    private final String classifier;
044
045    private final String extension;
046
047    private final Path path;
048
049    private final Map<String, String> properties;
050
051    /**
052     * Creates a new artifact with the specified coordinates. If not specified in the artifact coordinates, the
053     * artifact's extension defaults to {@code jar} and classifier to an empty string.
054     *
055     * @param coords The artifact coordinates in the format
056     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
057     *
058     * @throws IllegalArgumentException If the artifact coordinates found in {@code coords} do not match the expected
059     * format.
060     */
061    public DefaultArtifact(String coords) {
062        this(coords, null, null);
063    }
064
065    /**
066     * Creates a new artifact with the specified coordinates and properties. If not specified in the artifact
067     * coordinates, the artifact's extension defaults to {@code jar} and classifier to an empty string.
068     *
069     * @param coords The artifact coordinates in the format
070     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
071     * @param properties The artifact properties, may be {@code null}.
072     *
073     * @throws IllegalArgumentException If the artifact coordinates found in {@code coords} do not match the expected
074     * format.
075     */
076    public DefaultArtifact(String coords, Map<String, String> properties) {
077        this(coords, properties, null);
078    }
079
080    /**
081     * Creates a new artifact with the specified coordinates and type. If not specified in the artifact coordinates,
082     * the artifact's extension defaults to type extension (or "jar" if type is {@code null}) and
083     * classifier to type extension (or "" if type is {@code null}).
084     *
085     * @param coords The artifact coordinates in the format
086     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
087     * @param type The artifact type, may be {@code null}.
088     *
089     * @throws IllegalArgumentException If the artifact coordinates found in {@code coords} do not match the expected
090     * format.
091     */
092    public DefaultArtifact(String coords, ArtifactType type) {
093        this(coords, null, type);
094    }
095
096    /**
097     * Creates a new artifact with the specified coordinates, properties and type. If not specified in the artifact
098     * coordinates, the artifact's extension defaults to type extension (or "jar" if type is {@code null}) and
099     * classifier to type extension (or "" if type is {@code null}).
100     *
101     * @param coords The artifact coordinates in the format
102     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
103     * @param properties The artifact properties, may be {@code null}.
104     * @param type The artifact type, may be {@code null}.
105     *
106     * @throws IllegalArgumentException If the artifact coordinates found in {@code coords} do not match the expected
107     * format.
108     */
109    public DefaultArtifact(String coords, Map<String, String> properties, ArtifactType type) {
110        Matcher m = COORDINATE_PATTERN.matcher(coords);
111        if (!m.matches()) {
112            throw new IllegalArgumentException("Bad artifact coordinates " + coords
113                    + ", expected format is <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>");
114        }
115        groupId = m.group(1);
116        artifactId = m.group(2);
117        extension = get(m.group(4), type == null ? "jar" : type.getExtension());
118        classifier = get(m.group(6), type == null ? "" : type.getClassifier());
119        this.version = emptify(m.group(7));
120        this.path = null;
121        this.properties = mergeArtifactProperties(properties, (type != null) ? type.getProperties() : null);
122    }
123
124    private static String get(String value, String defaultValue) {
125        return (value == null || value.isEmpty()) ? defaultValue : value;
126    }
127
128    /**
129     * Creates a new artifact with the specified coordinates and no classifier. Passing {@code null} for any of the
130     * coordinates is equivalent to specifying an empty string.
131     *
132     * @param groupId The group identifier of the artifact, may be {@code null}.
133     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
134     * @param extension The file extension of the artifact, may be {@code null}.
135     * @param version The version (may also be a meta version or version range) of the artifact, may be {@code null}.
136     */
137    public DefaultArtifact(String groupId, String artifactId, String extension, String version) {
138        this(groupId, artifactId, "", extension, version);
139    }
140
141    /**
142     * Creates a new artifact with the specified coordinates. Passing {@code null} for any of the coordinates is
143     * equivalent to specifying an empty string.
144     *
145     * @param groupId The group identifier of the artifact, may be {@code null}.
146     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
147     * @param classifier The classifier of the artifact, may be {@code null}.
148     * @param extension The file extension of the artifact, may be {@code null}.
149     * @param version The version (may also be a meta version or version range) of the artifact, may be {@code null}.
150     */
151    public DefaultArtifact(String groupId, String artifactId, String classifier, String extension, String version) {
152        this(groupId, artifactId, classifier, extension, version, null, (File) null);
153    }
154
155    /**
156     * Creates a new artifact with the specified coordinates. Passing {@code null} for any of the coordinates is
157     * equivalent to specifying an empty string. The optional artifact type provided to this constructor will be used to
158     * determine the artifact's classifier and file extension if the corresponding arguments for this constructor are
159     * {@code null}.
160     *
161     * @param groupId The group identifier of the artifact, may be {@code null}.
162     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
163     * @param classifier The classifier of the artifact, may be {@code null}.
164     * @param extension The file extension of the artifact, may be {@code null}.
165     * @param version The version (may also be a meta version or version range) of the artifact, may be {@code null}.
166     * @param type The artifact type from which to query classifier, file extension and properties, may be {@code null}.
167     */
168    public DefaultArtifact(
169            String groupId, String artifactId, String classifier, String extension, String version, ArtifactType type) {
170        this(groupId, artifactId, classifier, extension, version, null, type);
171    }
172
173    /**
174     * Creates a new artifact with the specified coordinates and properties. Passing {@code null} for any of the
175     * coordinates is equivalent to specifying an empty string. The optional artifact type provided to this constructor
176     * will be used to determine the artifact's classifier and file extension if the corresponding arguments for this
177     * constructor are {@code null}. If the artifact type specifies properties, those will get merged with the
178     * properties passed directly into the constructor, with the latter properties taking precedence.
179     *
180     * @param groupId The group identifier of the artifact, may be {@code null}.
181     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
182     * @param classifier The classifier of the artifact, may be {@code null}.
183     * @param extension The file extension of the artifact, may be {@code null}.
184     * @param version The version (may also be a meta version or version range) of the artifact, may be {@code null}.
185     * @param properties The properties of the artifact, may be {@code null} if none.
186     * @param type The artifact type from which to query classifier, file extension and properties, may be {@code null}.
187     */
188    public DefaultArtifact(
189            String groupId,
190            String artifactId,
191            String classifier,
192            String extension,
193            String version,
194            Map<String, String> properties,
195            ArtifactType type) {
196        this.groupId = emptify(groupId);
197        this.artifactId = emptify(artifactId);
198        if (classifier != null || type == null) {
199            this.classifier = emptify(classifier);
200        } else {
201            this.classifier = emptify(type.getClassifier());
202        }
203        if (extension != null || type == null) {
204            this.extension = emptify(extension);
205        } else {
206            this.extension = emptify(type.getExtension());
207        }
208        this.version = emptify(version);
209        this.path = null;
210        this.properties = mergeArtifactProperties(properties, (type != null) ? type.getProperties() : null);
211    }
212
213    private static Map<String, String> mergeArtifactProperties(
214            Map<String, String> artifactProperties, Map<String, String> typeDefaultProperties) {
215        Map<String, String> properties;
216
217        if (artifactProperties == null || artifactProperties.isEmpty()) {
218            if (typeDefaultProperties == null || typeDefaultProperties.isEmpty()) {
219                properties = Collections.emptyMap();
220            } else {
221                // type default properties are already unmodifiable
222                return typeDefaultProperties;
223            }
224        } else {
225            properties = new HashMap<>();
226            if (typeDefaultProperties != null) {
227                properties.putAll(typeDefaultProperties);
228            }
229            properties.putAll(artifactProperties);
230            properties = Collections.unmodifiableMap(properties);
231        }
232
233        return properties;
234    }
235
236    /**
237     * Creates a new artifact with the specified coordinates, properties and file. Passing {@code null} for any of the
238     * coordinates is equivalent to specifying an empty string.
239     *
240     * @param groupId The group identifier of the artifact, may be {@code null}.
241     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
242     * @param classifier The classifier of the artifact, may be {@code null}.
243     * @param extension The file extension of the artifact, may be {@code null}.
244     * @param version The version of the artifact, may be {@code null}.
245     * @param properties The properties of the artifact, may be {@code null} if none.
246     * @param file The resolved file of the artifact, may be {@code null}.
247     */
248    public DefaultArtifact(
249            String groupId,
250            String artifactId,
251            String classifier,
252            String extension,
253            String version,
254            Map<String, String> properties,
255            File file) {
256        this.groupId = emptify(groupId);
257        this.artifactId = emptify(artifactId);
258        this.classifier = emptify(classifier);
259        this.extension = emptify(extension);
260        this.version = emptify(version);
261        this.path = file != null ? file.toPath() : null;
262        this.properties = copyProperties(properties);
263    }
264
265    /**
266     * Creates a new artifact with the specified coordinates, properties and file. Passing {@code null} for any of the
267     * coordinates is equivalent to specifying an empty string.
268     *
269     * @param groupId The group identifier of the artifact, may be {@code null}.
270     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
271     * @param classifier The classifier of the artifact, may be {@code null}.
272     * @param extension The file extension of the artifact, may be {@code null}.
273     * @param version The version of the artifact, may be {@code null}.
274     * @param properties The properties of the artifact, may be {@code null} if none.
275     * @param path The resolved file of the artifact, may be {@code null}.
276     */
277    public DefaultArtifact(
278            String groupId,
279            String artifactId,
280            String classifier,
281            String extension,
282            String version,
283            Map<String, String> properties,
284            Path path) {
285        this.groupId = emptify(groupId);
286        this.artifactId = emptify(artifactId);
287        this.classifier = emptify(classifier);
288        this.extension = emptify(extension);
289        this.version = emptify(version);
290        this.path = path;
291        this.properties = copyProperties(properties);
292    }
293
294    DefaultArtifact(
295            String groupId,
296            String artifactId,
297            String classifier,
298            String extension,
299            String version,
300            Path path,
301            Map<String, String> properties) {
302        // NOTE: This constructor assumes immutability of the provided properties, for internal use only
303        this.groupId = emptify(groupId);
304        this.artifactId = emptify(artifactId);
305        this.classifier = emptify(classifier);
306        this.extension = emptify(extension);
307        this.version = emptify(version);
308        this.path = path;
309        this.properties = properties;
310    }
311
312    private static String emptify(String str) {
313        return (str == null) ? "" : str;
314    }
315
316    public String getGroupId() {
317        return groupId;
318    }
319
320    public String getArtifactId() {
321        return artifactId;
322    }
323
324    public String getVersion() {
325        return version;
326    }
327
328    public String getClassifier() {
329        return classifier;
330    }
331
332    public String getExtension() {
333        return extension;
334    }
335
336    @Deprecated
337    public File getFile() {
338        return path != null ? path.toFile() : null;
339    }
340
341    public Path getPath() {
342        return path;
343    }
344
345    public Map<String, String> getProperties() {
346        return properties;
347    }
348}