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            if (artifactProperties != null) {
230                properties.putAll(artifactProperties);
231            }
232            properties = Collections.unmodifiableMap(properties);
233        }
234
235        return properties;
236    }
237
238    /**
239     * Creates a new artifact with the specified coordinates, properties and file. Passing {@code null} for any of the
240     * coordinates is equivalent to specifying an empty string.
241     *
242     * @param groupId The group identifier of the artifact, may be {@code null}.
243     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
244     * @param classifier The classifier of the artifact, may be {@code null}.
245     * @param extension The file extension of the artifact, may be {@code null}.
246     * @param version The version of the artifact, may be {@code null}.
247     * @param properties The properties of the artifact, may be {@code null} if none.
248     * @param file The resolved file of the artifact, may be {@code null}.
249     */
250    public DefaultArtifact(
251            String groupId,
252            String artifactId,
253            String classifier,
254            String extension,
255            String version,
256            Map<String, String> properties,
257            File file) {
258        this.groupId = emptify(groupId);
259        this.artifactId = emptify(artifactId);
260        this.classifier = emptify(classifier);
261        this.extension = emptify(extension);
262        this.version = emptify(version);
263        this.path = file != null ? file.toPath() : null;
264        this.properties = copyProperties(properties);
265    }
266
267    /**
268     * Creates a new artifact with the specified coordinates, properties and file. Passing {@code null} for any of the
269     * coordinates is equivalent to specifying an empty string.
270     *
271     * @param groupId The group identifier of the artifact, may be {@code null}.
272     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
273     * @param classifier The classifier of the artifact, may be {@code null}.
274     * @param extension The file extension of the artifact, may be {@code null}.
275     * @param version The version of the artifact, may be {@code null}.
276     * @param properties The properties of the artifact, may be {@code null} if none.
277     * @param path The resolved file of the artifact, may be {@code null}.
278     */
279    public DefaultArtifact(
280            String groupId,
281            String artifactId,
282            String classifier,
283            String extension,
284            String version,
285            Map<String, String> properties,
286            Path path) {
287        this.groupId = emptify(groupId);
288        this.artifactId = emptify(artifactId);
289        this.classifier = emptify(classifier);
290        this.extension = emptify(extension);
291        this.version = emptify(version);
292        this.path = path;
293        this.properties = copyProperties(properties);
294    }
295
296    DefaultArtifact(
297            String groupId,
298            String artifactId,
299            String classifier,
300            String extension,
301            String version,
302            Path path,
303            Map<String, String> properties) {
304        // NOTE: This constructor assumes immutability of the provided properties, for internal use only
305        this.groupId = emptify(groupId);
306        this.artifactId = emptify(artifactId);
307        this.classifier = emptify(classifier);
308        this.extension = emptify(extension);
309        this.version = emptify(version);
310        this.path = path;
311        this.properties = properties;
312    }
313
314    private static String emptify(String str) {
315        return (str == null) ? "" : str;
316    }
317
318    public String getGroupId() {
319        return groupId;
320    }
321
322    public String getArtifactId() {
323        return artifactId;
324    }
325
326    public String getVersion() {
327        return version;
328    }
329
330    public String getClassifier() {
331        return classifier;
332    }
333
334    public String getExtension() {
335        return extension;
336    }
337
338    @Deprecated
339    public File getFile() {
340        return path != null ? path.toFile() : null;
341    }
342
343    public Path getPath() {
344        return path;
345    }
346
347    public Map<String, String> getProperties() {
348        return properties;
349    }
350}