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 = mergeArtifactProperties(properties, (type != null) ? type.getProperties() : null);
177    }
178
179    private static Map<String, String> mergeArtifactProperties(
180            Map<String, String> artifactProperties, Map<String, String> typeDefaultProperties) {
181        Map<String, String> properties;
182
183        if (artifactProperties == null || artifactProperties.isEmpty()) {
184            if (typeDefaultProperties == null || typeDefaultProperties.isEmpty()) {
185                properties = Collections.emptyMap();
186            } else {
187                // type default properties are already unmodifiable
188                return typeDefaultProperties;
189            }
190        } else {
191            properties = new HashMap<>();
192            if (typeDefaultProperties != null) {
193                properties.putAll(typeDefaultProperties);
194            }
195            if (artifactProperties != null) {
196                properties.putAll(artifactProperties);
197            }
198            properties = Collections.unmodifiableMap(properties);
199        }
200
201        return properties;
202    }
203
204    /**
205     * Creates a new artifact with the specified coordinates, properties and file. Passing {@code null} for any of the
206     * coordinates is equivalent to specifying an empty string.
207     *
208     * @param groupId The group identifier of the artifact, may be {@code null}.
209     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
210     * @param classifier The classifier of the artifact, may be {@code null}.
211     * @param extension The file extension of the artifact, may be {@code null}.
212     * @param version The version of the artifact, may be {@code null}.
213     * @param properties The properties of the artifact, may be {@code null} if none.
214     * @param file The resolved file of the artifact, may be {@code null}.
215     */
216    public DefaultArtifact(
217            String groupId,
218            String artifactId,
219            String classifier,
220            String extension,
221            String version,
222            Map<String, String> properties,
223            File file) {
224        this.groupId = emptify(groupId);
225        this.artifactId = emptify(artifactId);
226        this.classifier = emptify(classifier);
227        this.extension = emptify(extension);
228        this.version = emptify(version);
229        this.file = file;
230        this.properties = copyProperties(properties);
231    }
232
233    DefaultArtifact(
234            String groupId,
235            String artifactId,
236            String classifier,
237            String extension,
238            String version,
239            File file,
240            Map<String, String> properties) {
241        // NOTE: This constructor assumes immutability of the provided properties, for internal use only
242        this.groupId = emptify(groupId);
243        this.artifactId = emptify(artifactId);
244        this.classifier = emptify(classifier);
245        this.extension = emptify(extension);
246        this.version = emptify(version);
247        this.file = file;
248        this.properties = properties;
249    }
250
251    private static String emptify(String str) {
252        return (str == null) ? "" : str;
253    }
254
255    public String getGroupId() {
256        return groupId;
257    }
258
259    public String getArtifactId() {
260        return artifactId;
261    }
262
263    public String getVersion() {
264        return version;
265    }
266
267    public String getClassifier() {
268        return classifier;
269    }
270
271    public String getExtension() {
272        return extension;
273    }
274
275    public File getFile() {
276        return file;
277    }
278
279    public Map<String, String> getProperties() {
280        return properties;
281    }
282}