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