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.util.Collections;
023import java.util.HashMap;
024import java.util.Map;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028/**
029 * A simple artifact. <em>Note:</em> Instances of this class are immutable and the exposed mutators return new objects
030 * rather than changing the current instance.
031 */
032public final class DefaultArtifact extends AbstractArtifact {
033    private static final Pattern COORDINATE_PATTERN =
034            Pattern.compile("([^: ]+):([^: ]+)(:([^: ]*)(:([^: ]+))?)?:([^: ]+)");
035
036    private final String groupId;
037
038    private final String artifactId;
039
040    private final String version;
041
042    private final String classifier;
043
044    private final String extension;
045
046    private final File file;
047
048    private final Map<String, String> properties;
049
050    /**
051     * Creates a new artifact with the specified coordinates. If not specified in the artifact coordinates, the
052     * artifact's extension defaults to {@code jar} and classifier to an empty string.
053     *
054     * @param coords The artifact coordinates in the format
055     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
056     *
057     * @throws IllegalArgumentException If the artifact coordinates found in {@code coords} do not match the expected
058     * format.
059     */
060    public DefaultArtifact(String coords) {
061        this(coords, null, null);
062    }
063
064    /**
065     * Creates a new artifact with the specified coordinates and properties. If not specified in the artifact
066     * coordinates, the artifact's extension defaults to {@code jar} and classifier to an empty string.
067     *
068     * @param coords The artifact coordinates in the format
069     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
070     * @param properties The artifact properties, may be {@code null}.
071     *
072     * @throws IllegalArgumentException If the artifact coordinates found in {@code coords} do not match the expected
073     * format.
074     */
075    public DefaultArtifact(String coords, Map<String, String> properties) {
076        this(coords, properties, null);
077    }
078
079    /**
080     * Creates a new artifact with the specified coordinates and type. If not specified in the artifact coordinates,
081     * the artifact's extension defaults to type extension (or "jar" if type is {@code null}) and
082     * classifier to type extension (or "" if type is {@code null}).
083     *
084     * @param coords The artifact coordinates in the format
085     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
086     * @param type The artifact type, may be {@code null}.
087     *
088     * @throws IllegalArgumentException If the artifact coordinates found in {@code coords} do not match the expected
089     * format.
090     */
091    public DefaultArtifact(String coords, ArtifactType type) {
092        this(coords, null, type);
093    }
094
095    /**
096     * Creates a new artifact with the specified coordinates, properties and type. If not specified in the artifact
097     * coordinates, the artifact's extension defaults to type extension (or "jar" if type is {@code null}) and
098     * classifier to type extension (or "" if type is {@code null}).
099     *
100     * @param coords The artifact coordinates in the format
101     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
102     * @param properties The artifact properties, may be {@code null}.
103     * @param type The artifact type, may be {@code null}.
104     *
105     * @throws IllegalArgumentException If the artifact coordinates found in {@code coords} do not match the expected
106     * format.
107     */
108    public DefaultArtifact(String coords, Map<String, String> properties, ArtifactType type) {
109        Matcher m = COORDINATE_PATTERN.matcher(coords);
110        if (!m.matches()) {
111            throw new IllegalArgumentException("Bad artifact coordinates " + coords
112                    + ", expected format is <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>");
113        }
114        groupId = m.group(1);
115        artifactId = m.group(2);
116        extension = get(m.group(4), type == null ? "jar" : type.getExtension());
117        classifier = get(m.group(6), type == null ? "" : type.getClassifier());
118        this.version = emptify(m.group(7));
119        this.file = null;
120        this.properties = merge(properties, (type != null) ? type.getProperties() : null);
121    }
122
123    private static String get(String value, String defaultValue) {
124        return (value == null || value.isEmpty()) ? defaultValue : value;
125    }
126
127    /**
128     * Creates a new artifact with the specified coordinates and no classifier. Passing {@code null} for any of the
129     * coordinates is equivalent to specifying an empty string.
130     *
131     * @param groupId The group identifier of the artifact, may be {@code null}.
132     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
133     * @param extension The file extension of the artifact, may be {@code null}.
134     * @param version The version of the artifact, may be {@code null}.
135     */
136    public DefaultArtifact(String groupId, String artifactId, String extension, String version) {
137        this(groupId, artifactId, "", extension, version);
138    }
139
140    /**
141     * Creates a new artifact with the specified coordinates. Passing {@code null} for any of the coordinates is
142     * equivalent to specifying an empty string.
143     *
144     * @param groupId The group identifier of the artifact, may be {@code null}.
145     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
146     * @param classifier The classifier of the artifact, may be {@code null}.
147     * @param extension The file extension of the artifact, may be {@code null}.
148     * @param version The version of the artifact, may be {@code null}.
149     */
150    public DefaultArtifact(String groupId, String artifactId, String classifier, String extension, String version) {
151        this(groupId, artifactId, classifier, extension, version, null, (File) null);
152    }
153
154    /**
155     * Creates a new artifact with the specified coordinates. Passing {@code null} for any of the coordinates is
156     * equivalent to specifying an empty string. The optional artifact type provided to this constructor will be used to
157     * determine the artifact's classifier and file extension if the corresponding arguments for this constructor are
158     * {@code null}.
159     *
160     * @param groupId The group identifier of the artifact, may be {@code null}.
161     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
162     * @param classifier The classifier of the artifact, may be {@code null}.
163     * @param extension The file extension of the artifact, may be {@code null}.
164     * @param version The version of the artifact, may be {@code null}.
165     * @param type The artifact type from which to query classifier, file extension and properties, may be {@code null}.
166     */
167    public DefaultArtifact(
168            String groupId, String artifactId, String classifier, String extension, String version, ArtifactType type) {
169        this(groupId, artifactId, classifier, extension, version, null, type);
170    }
171
172    /**
173     * Creates a new artifact with the specified coordinates and properties. Passing {@code null} for any of the
174     * coordinates is equivalent to specifying an empty string. The optional artifact type provided to this constructor
175     * will be used to determine the artifact's classifier and file extension if the corresponding arguments for this
176     * constructor are {@code null}. If the artifact type specifies properties, those will get merged with the
177     * properties passed directly into the constructor, with the latter properties taking precedence.
178     *
179     * @param groupId The group identifier of the artifact, may be {@code null}.
180     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
181     * @param classifier The classifier of the artifact, may be {@code null}.
182     * @param extension The file extension of the artifact, may be {@code null}.
183     * @param version The version of the artifact, may be {@code null}.
184     * @param properties The properties of the artifact, may be {@code null} if none.
185     * @param type The artifact type from which to query classifier, file extension and properties, may be {@code null}.
186     */
187    public DefaultArtifact(
188            String groupId,
189            String artifactId,
190            String classifier,
191            String extension,
192            String version,
193            Map<String, String> properties,
194            ArtifactType type) {
195        this.groupId = emptify(groupId);
196        this.artifactId = emptify(artifactId);
197        if (classifier != null || type == null) {
198            this.classifier = emptify(classifier);
199        } else {
200            this.classifier = emptify(type.getClassifier());
201        }
202        if (extension != null || type == null) {
203            this.extension = emptify(extension);
204        } else {
205            this.extension = emptify(type.getExtension());
206        }
207        this.version = emptify(version);
208        this.file = null;
209        this.properties = merge(properties, (type != null) ? type.getProperties() : null);
210    }
211
212    private static Map<String, String> merge(Map<String, String> dominant, Map<String, String> recessive) {
213        Map<String, String> properties;
214
215        if ((dominant == null || dominant.isEmpty()) && (recessive == null || recessive.isEmpty())) {
216            properties = Collections.emptyMap();
217        } else {
218            properties = new HashMap<>();
219            if (recessive != null) {
220                properties.putAll(recessive);
221            }
222            if (dominant != null) {
223                properties.putAll(dominant);
224            }
225            properties = Collections.unmodifiableMap(properties);
226        }
227
228        return properties;
229    }
230
231    /**
232     * Creates a new artifact with the specified coordinates, properties and file. Passing {@code null} for any of the
233     * coordinates is equivalent to specifying an empty string.
234     *
235     * @param groupId The group identifier of the artifact, may be {@code null}.
236     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
237     * @param classifier The classifier of the artifact, may be {@code null}.
238     * @param extension The file extension of the artifact, may be {@code null}.
239     * @param version The version of the artifact, may be {@code null}.
240     * @param properties The properties of the artifact, may be {@code null} if none.
241     * @param file The resolved file of the artifact, may be {@code null}.
242     */
243    public DefaultArtifact(
244            String groupId,
245            String artifactId,
246            String classifier,
247            String extension,
248            String version,
249            Map<String, String> properties,
250            File file) {
251        this.groupId = emptify(groupId);
252        this.artifactId = emptify(artifactId);
253        this.classifier = emptify(classifier);
254        this.extension = emptify(extension);
255        this.version = emptify(version);
256        this.file = file;
257        this.properties = copyProperties(properties);
258    }
259
260    DefaultArtifact(
261            String groupId,
262            String artifactId,
263            String classifier,
264            String extension,
265            String version,
266            File file,
267            Map<String, String> properties) {
268        // NOTE: This constructor assumes immutability of the provided properties, for internal use only
269        this.groupId = emptify(groupId);
270        this.artifactId = emptify(artifactId);
271        this.classifier = emptify(classifier);
272        this.extension = emptify(extension);
273        this.version = emptify(version);
274        this.file = file;
275        this.properties = properties;
276    }
277
278    private static String emptify(String str) {
279        return (str == null) ? "" : str;
280    }
281
282    public String getGroupId() {
283        return groupId;
284    }
285
286    public String getArtifactId() {
287        return artifactId;
288    }
289
290    public String getVersion() {
291        return version;
292    }
293
294    public String getClassifier() {
295        return classifier;
296    }
297
298    public String getExtension() {
299        return extension;
300    }
301
302    public File getFile() {
303        return file;
304    }
305
306    public Map<String, String> getProperties() {
307        return properties;
308    }
309}