001package org.eclipse.aether.artifact;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 * 
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 * 
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
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
034    extends AbstractArtifact
035{
036    private static final Pattern COORDINATE_PATTERN =
037        Pattern.compile( "([^: ]+):([^: ]+)(:([^: ]*)(:([^: ]+))?)?:([^: ]+)" );
038
039    private final String groupId;
040
041    private final String artifactId;
042
043    private final String version;
044
045    private final String classifier;
046
047    private final String extension;
048
049    private final File file;
050
051    private final Map<String, String> properties;
052
053    /**
054     * Creates a new artifact with the specified coordinates. If not specified in the artifact coordinates, the
055     * artifact's extension defaults to {@code jar} and classifier to an empty string.
056     * 
057     * @param coords The artifact coordinates in the format
058     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
059     *
060     * @throws IllegalArgumentException If the artifact coordinates found in {@code coords} do not match the expected
061     * format.
062     */
063    public DefaultArtifact( String coords )
064    {
065        this( coords, Collections.<String, String>emptyMap() );
066    }
067
068    /**
069     * Creates a new artifact with the specified coordinates and properties. If not specified in the artifact
070     * coordinates, the artifact's extension defaults to {@code jar} and classifier to an empty string.
071     * 
072     * @param coords The artifact coordinates in the format
073     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
074     * @param properties The artifact properties, may be {@code null}.
075     *
076     * @throws IllegalArgumentException If the artifact coordinates found in {@code coords} do not match the expected
077     * format.
078     */
079    public DefaultArtifact( String coords, Map<String, String> properties )
080    {
081        Matcher m = COORDINATE_PATTERN.matcher( coords );
082        if ( !m.matches() )
083        {
084            throw new IllegalArgumentException( "Bad artifact coordinates " + coords
085                + ", expected format is <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>" );
086        }
087        groupId = m.group( 1 );
088        artifactId = m.group( 2 );
089        extension = get( m.group( 4 ), "jar" );
090        classifier = get( m.group( 6 ), "" );
091        version = m.group( 7 );
092        file = null;
093        this.properties = copyProperties( properties );
094    }
095
096    private static String get( String value, String defaultValue )
097    {
098        return ( value == null || value.isEmpty() ) ? defaultValue : value;
099    }
100
101    /**
102     * Creates a new artifact with the specified coordinates and no classifier. Passing {@code null} for any of the
103     * coordinates is equivalent to specifying an empty string.
104     * 
105     * @param groupId The group identifier of the artifact, may be {@code null}.
106     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
107     * @param extension The file extension of the artifact, may be {@code null}.
108     * @param version The version of the artifact, may be {@code null}.
109     */
110    public DefaultArtifact( String groupId, String artifactId, String extension, String version )
111    {
112        this( groupId, artifactId, "", extension, version );
113    }
114
115    /**
116     * Creates a new artifact with the specified coordinates. Passing {@code null} for any of the coordinates is
117     * equivalent to specifying an empty string.
118     * 
119     * @param groupId The group identifier of the artifact, may be {@code null}.
120     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
121     * @param classifier The classifier of the artifact, may be {@code null}.
122     * @param extension The file extension of the artifact, may be {@code null}.
123     * @param version The version of the artifact, may be {@code null}.
124     */
125    public DefaultArtifact( String groupId, String artifactId, String classifier, String extension, String version )
126    {
127        this( groupId, artifactId, classifier, extension, version, null, (File) null );
128    }
129
130    /**
131     * Creates a new artifact with the specified coordinates. Passing {@code null} for any of the coordinates is
132     * equivalent to specifying an empty string. The optional artifact type provided to this constructor will be used to
133     * determine the artifact's classifier and file extension if the corresponding arguments for this constructor are
134     * {@code null}.
135     * 
136     * @param groupId The group identifier of the artifact, may be {@code null}.
137     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
138     * @param classifier The classifier of the artifact, may be {@code null}.
139     * @param extension The file extension of the artifact, may be {@code null}.
140     * @param version The version of the artifact, may be {@code null}.
141     * @param type The artifact type from which to query classifier, file extension and properties, may be {@code null}.
142     */
143    public DefaultArtifact( String groupId, String artifactId, String classifier, String extension, String version,
144                            ArtifactType type )
145    {
146        this( groupId, artifactId, classifier, extension, version, null, type );
147    }
148
149    /**
150     * Creates a new artifact with the specified coordinates and properties. Passing {@code null} for any of the
151     * coordinates is equivalent to specifying an empty string. The optional artifact type provided to this constructor
152     * will be used to determine the artifact's classifier and file extension if the corresponding arguments for this
153     * constructor are {@code null}. If the artifact type specifies properties, those will get merged with the
154     * properties passed directly into the constructor, with the latter properties taking precedence.
155     * 
156     * @param groupId The group identifier of the artifact, may be {@code null}.
157     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
158     * @param classifier The classifier of the artifact, may be {@code null}.
159     * @param extension The file extension of the artifact, may be {@code null}.
160     * @param version The version of the artifact, may be {@code null}.
161     * @param properties The properties of the artifact, may be {@code null} if none.
162     * @param type The artifact type from which to query classifier, file extension and properties, may be {@code null}.
163     */
164    public DefaultArtifact( String groupId, String artifactId, String classifier, String extension, String version,
165                            Map<String, String> properties, ArtifactType type )
166    {
167        this.groupId = emptify( groupId );
168        this.artifactId = emptify( artifactId );
169        if ( classifier != null || type == null )
170        {
171            this.classifier = emptify( classifier );
172        }
173        else
174        {
175            this.classifier = emptify( type.getClassifier() );
176        }
177        if ( extension != null || type == null )
178        {
179            this.extension = emptify( extension );
180        }
181        else
182        {
183            this.extension = emptify( type.getExtension() );
184        }
185        this.version = emptify( version );
186        this.file = null;
187        this.properties = merge( properties, ( type != null ) ? type.getProperties() : null );
188    }
189
190    private static Map<String, String> merge( Map<String, String> dominant, Map<String, String> recessive )
191    {
192        Map<String, String> properties;
193
194        if ( ( dominant == null || dominant.isEmpty() ) && ( recessive == null || recessive.isEmpty() ) )
195        {
196            properties = Collections.emptyMap();
197        }
198        else
199        {
200            properties = new HashMap<>();
201            if ( recessive != null )
202            {
203                properties.putAll( recessive );
204            }
205            if ( dominant != null )
206            {
207                properties.putAll( dominant );
208            }
209            properties = Collections.unmodifiableMap( properties );
210        }
211
212        return properties;
213    }
214
215    /**
216     * Creates a new artifact with the specified coordinates, properties and file. Passing {@code null} for any of the
217     * coordinates is equivalent to specifying an empty string.
218     * 
219     * @param groupId The group identifier of the artifact, may be {@code null}.
220     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
221     * @param classifier The classifier of the artifact, may be {@code null}.
222     * @param extension The file extension of the artifact, may be {@code null}.
223     * @param version The version of the artifact, may be {@code null}.
224     * @param properties The properties of the artifact, may be {@code null} if none.
225     * @param file The resolved file of the artifact, may be {@code null}.
226     */
227    public DefaultArtifact( String groupId, String artifactId, String classifier, String extension, String version,
228                            Map<String, String> properties, File file )
229    {
230        this.groupId = emptify( groupId );
231        this.artifactId = emptify( artifactId );
232        this.classifier = emptify( classifier );
233        this.extension = emptify( extension );
234        this.version = emptify( version );
235        this.file = file;
236        this.properties = copyProperties( properties );
237    }
238
239    DefaultArtifact( String groupId, String artifactId, String classifier, String extension, String version, File file,
240                     Map<String, String> properties )
241    {
242        // NOTE: This constructor assumes immutability of the provided properties, for internal use only
243        this.groupId = emptify( groupId );
244        this.artifactId = emptify( artifactId );
245        this.classifier = emptify( classifier );
246        this.extension = emptify( extension );
247        this.version = emptify( version );
248        this.file = file;
249        this.properties = properties;
250    }
251
252    private static String emptify( String str )
253    {
254        return ( str == null ) ? "" : str;
255    }
256
257    public String getGroupId()
258    {
259        return groupId;
260    }
261
262    public String getArtifactId()
263    {
264        return artifactId;
265    }
266
267    public String getVersion()
268    {
269        return version;
270    }
271
272    public String getClassifier()
273    {
274        return classifier;
275    }
276
277    public String getExtension()
278    {
279        return extension;
280    }
281
282    public File getFile()
283    {
284        return file;
285    }
286
287    public Map<String, String> getProperties()
288    {
289        return properties;
290    }
291
292}