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