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, Collections.<String, String>emptyMap());
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        Matcher m = COORDINATE_PATTERN.matcher(coords);
077        if (!m.matches()) {
078            throw new IllegalArgumentException("Bad artifact coordinates " + coords
079                    + ", expected format is <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>");
080        }
081        groupId = m.group(1);
082        artifactId = m.group(2);
083        extension = get(m.group(4), "jar");
084        classifier = get(m.group(6), "");
085        version = m.group(7);
086        file = null;
087        this.properties = copyProperties(properties);
088    }
089
090    private static String get(String value, String defaultValue) {
091        return (value == null || value.isEmpty()) ? defaultValue : value;
092    }
093
094    /**
095     * Creates a new artifact with the specified coordinates and no classifier. Passing {@code null} for any of the
096     * coordinates is equivalent to specifying an empty string.
097     *
098     * @param groupId The group identifier of the artifact, may be {@code null}.
099     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
100     * @param extension The file extension of the artifact, may be {@code null}.
101     * @param version The version of the artifact, may be {@code null}.
102     */
103    public DefaultArtifact(String groupId, String artifactId, String extension, String version) {
104        this(groupId, artifactId, "", extension, version);
105    }
106
107    /**
108     * Creates a new artifact with the specified coordinates. Passing {@code null} for any of the coordinates is
109     * equivalent to specifying an empty string.
110     *
111     * @param groupId The group identifier of the artifact, may be {@code null}.
112     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
113     * @param classifier The classifier of the artifact, may be {@code null}.
114     * @param extension The file extension of the artifact, may be {@code null}.
115     * @param version The version of the artifact, may be {@code null}.
116     */
117    public DefaultArtifact(String groupId, String artifactId, String classifier, String extension, String version) {
118        this(groupId, artifactId, classifier, extension, version, null, (File) null);
119    }
120
121    /**
122     * Creates a new artifact with the specified coordinates. Passing {@code null} for any of the coordinates is
123     * equivalent to specifying an empty string. The optional artifact type provided to this constructor will be used to
124     * determine the artifact's classifier and file extension if the corresponding arguments for this constructor are
125     * {@code null}.
126     *
127     * @param groupId The group identifier of the artifact, may be {@code null}.
128     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
129     * @param classifier The classifier of the artifact, may be {@code null}.
130     * @param extension The file extension of the artifact, may be {@code null}.
131     * @param version The version of the artifact, may be {@code null}.
132     * @param type The artifact type from which to query classifier, file extension and properties, may be {@code null}.
133     */
134    public DefaultArtifact(
135            String groupId, String artifactId, String classifier, String extension, String version, ArtifactType type) {
136        this(groupId, artifactId, classifier, extension, version, null, type);
137    }
138
139    /**
140     * Creates a new artifact with the specified coordinates and properties. Passing {@code null} for any of the
141     * coordinates is equivalent to specifying an empty string. The optional artifact type provided to this constructor
142     * will be used to determine the artifact's classifier and file extension if the corresponding arguments for this
143     * constructor are {@code null}. If the artifact type specifies properties, those will get merged with the
144     * properties passed directly into the constructor, with the latter properties taking precedence.
145     *
146     * @param groupId The group identifier of the artifact, may be {@code null}.
147     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
148     * @param classifier The classifier of the artifact, may be {@code null}.
149     * @param extension The file extension of the artifact, may be {@code null}.
150     * @param version The version of the artifact, may be {@code null}.
151     * @param properties The properties of the artifact, may be {@code null} if none.
152     * @param type The artifact type from which to query classifier, file extension and properties, may be {@code null}.
153     */
154    public DefaultArtifact(
155            String groupId,
156            String artifactId,
157            String classifier,
158            String extension,
159            String version,
160            Map<String, String> properties,
161            ArtifactType type) {
162        this.groupId = emptify(groupId);
163        this.artifactId = emptify(artifactId);
164        if (classifier != null || type == null) {
165            this.classifier = emptify(classifier);
166        } else {
167            this.classifier = emptify(type.getClassifier());
168        }
169        if (extension != null || type == null) {
170            this.extension = emptify(extension);
171        } else {
172            this.extension = emptify(type.getExtension());
173        }
174        this.version = emptify(version);
175        this.file = null;
176        this.properties = merge(properties, (type != null) ? type.getProperties() : null);
177    }
178
179    private static Map<String, String> merge(Map<String, String> dominant, Map<String, String> recessive) {
180        Map<String, String> properties;
181
182        if ((dominant == null || dominant.isEmpty()) && (recessive == null || recessive.isEmpty())) {
183            properties = Collections.emptyMap();
184        } else {
185            properties = new HashMap<>();
186            if (recessive != null) {
187                properties.putAll(recessive);
188            }
189            if (dominant != null) {
190                properties.putAll(dominant);
191            }
192            properties = Collections.unmodifiableMap(properties);
193        }
194
195        return properties;
196    }
197
198    /**
199     * Creates a new artifact with the specified coordinates, properties and file. Passing {@code null} for any of the
200     * coordinates is equivalent to specifying an empty string.
201     *
202     * @param groupId The group identifier of the artifact, may be {@code null}.
203     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
204     * @param classifier The classifier of the artifact, may be {@code null}.
205     * @param extension The file extension of the artifact, may be {@code null}.
206     * @param version The version of the artifact, may be {@code null}.
207     * @param properties The properties of the artifact, may be {@code null} if none.
208     * @param file The resolved file of the artifact, may be {@code null}.
209     */
210    public DefaultArtifact(
211            String groupId,
212            String artifactId,
213            String classifier,
214            String extension,
215            String version,
216            Map<String, String> properties,
217            File file) {
218        this.groupId = emptify(groupId);
219        this.artifactId = emptify(artifactId);
220        this.classifier = emptify(classifier);
221        this.extension = emptify(extension);
222        this.version = emptify(version);
223        this.file = file;
224        this.properties = copyProperties(properties);
225    }
226
227    DefaultArtifact(
228            String groupId,
229            String artifactId,
230            String classifier,
231            String extension,
232            String version,
233            File file,
234            Map<String, String> properties) {
235        // NOTE: This constructor assumes immutability of the provided properties, for internal use only
236        this.groupId = emptify(groupId);
237        this.artifactId = emptify(artifactId);
238        this.classifier = emptify(classifier);
239        this.extension = emptify(extension);
240        this.version = emptify(version);
241        this.file = file;
242        this.properties = properties;
243    }
244
245    private static String emptify(String str) {
246        return (str == null) ? "" : str;
247    }
248
249    public String getGroupId() {
250        return groupId;
251    }
252
253    public String getArtifactId() {
254        return artifactId;
255    }
256
257    public String getVersion() {
258        return version;
259    }
260
261    public String getClassifier() {
262        return classifier;
263    }
264
265    public String getExtension() {
266        return extension;
267    }
268
269    public File getFile() {
270        return file;
271    }
272
273    public Map<String, String> getProperties() {
274        return properties;
275    }
276}