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
037    private final String groupId;
038
039    private final String artifactId;
040
041    private final String version;
042
043    private final String classifier;
044
045    private final String extension;
046
047    private final File file;
048
049    private final Map<String, String> properties;
050
051    /**
052     * Creates a new artifact with the specified coordinates. If not specified in the artifact coordinates, the
053     * artifact's extension defaults to {@code jar} and classifier to an empty string.
054     * 
055     * @param coords The artifact coordinates in the format
056     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
057     */
058    public DefaultArtifact( String coords )
059    {
060        this( coords, Collections.<String, String>emptyMap() );
061    }
062
063    /**
064     * Creates a new artifact with the specified coordinates and properties. If not specified in the artifact
065     * coordinates, the artifact's extension defaults to {@code jar} and classifier to an empty string.
066     * 
067     * @param coords The artifact coordinates in the format
068     *            {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
069     * @param properties The artifact properties, may be {@code null}.
070     */
071    public DefaultArtifact( String coords, Map<String, String> properties )
072    {
073        Pattern p = Pattern.compile( "([^: ]+):([^: ]+)(:([^: ]*)(:([^: ]+))?)?:([^: ]+)" );
074        Matcher m = p.matcher( coords );
075        if ( !m.matches() )
076        {
077            throw new IllegalArgumentException( "Bad artifact coordinates " + coords
078                + ", expected format is <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>" );
079        }
080        groupId = m.group( 1 );
081        artifactId = m.group( 2 );
082        extension = get( m.group( 4 ), "jar" );
083        classifier = get( m.group( 6 ), "" );
084        version = m.group( 7 );
085        file = null;
086        this.properties = copyProperties( properties );
087    }
088
089    private static String get( String value, String defaultValue )
090    {
091        return ( value == null || value.length() <= 0 ) ? 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    {
105        this( groupId, artifactId, "", extension, version );
106    }
107
108    /**
109     * Creates a new artifact with the specified coordinates. Passing {@code null} for any of the coordinates is
110     * equivalent to specifying an empty string.
111     * 
112     * @param groupId The group identifier of the artifact, may be {@code null}.
113     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
114     * @param classifier The classifier of the artifact, may be {@code null}.
115     * @param extension The file extension of the artifact, may be {@code null}.
116     * @param version The version of the artifact, may be {@code null}.
117     */
118    public DefaultArtifact( String groupId, String artifactId, String classifier, String extension, String version )
119    {
120        this( groupId, artifactId, classifier, extension, version, null, (File) null );
121    }
122
123    /**
124     * Creates a new artifact with the specified coordinates. Passing {@code null} for any of the coordinates is
125     * equivalent to specifying an empty string. The optional artifact type provided to this constructor will be used to
126     * determine the artifact's classifier and file extension if the corresponding arguments for this constructor are
127     * {@code null}.
128     * 
129     * @param groupId The group identifier of the artifact, may be {@code null}.
130     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
131     * @param classifier The classifier of the artifact, may be {@code null}.
132     * @param extension The file extension of the artifact, may be {@code null}.
133     * @param version The version of the artifact, may be {@code null}.
134     * @param type The artifact type from which to query classifier, file extension and properties, may be {@code null}.
135     */
136    public DefaultArtifact( String groupId, String artifactId, String classifier, String extension, String version,
137                            ArtifactType type )
138    {
139        this( groupId, artifactId, classifier, extension, version, null, type );
140    }
141
142    /**
143     * Creates a new artifact with the specified coordinates and properties. Passing {@code null} for any of the
144     * coordinates is equivalent to specifying an empty string. The optional artifact type provided to this constructor
145     * will be used to determine the artifact's classifier and file extension if the corresponding arguments for this
146     * constructor are {@code null}. If the artifact type specifies properties, those will get merged with the
147     * properties passed directly into the constructor, with the latter properties taking precedence.
148     * 
149     * @param groupId The group identifier of the artifact, may be {@code null}.
150     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
151     * @param classifier The classifier of the artifact, may be {@code null}.
152     * @param extension The file extension of the artifact, may be {@code null}.
153     * @param version The version of the artifact, may be {@code null}.
154     * @param properties The properties of the artifact, may be {@code null} if none.
155     * @param type The artifact type from which to query classifier, file extension and properties, may be {@code null}.
156     */
157    public DefaultArtifact( String groupId, String artifactId, String classifier, String extension, String version,
158                            Map<String, String> properties, ArtifactType type )
159    {
160        this.groupId = emptify( groupId );
161        this.artifactId = emptify( artifactId );
162        if ( classifier != null || type == null )
163        {
164            this.classifier = emptify( classifier );
165        }
166        else
167        {
168            this.classifier = emptify( type.getClassifier() );
169        }
170        if ( extension != null || type == null )
171        {
172            this.extension = emptify( extension );
173        }
174        else
175        {
176            this.extension = emptify( type.getExtension() );
177        }
178        this.version = emptify( version );
179        this.file = null;
180        this.properties = merge( properties, ( type != null ) ? type.getProperties() : null );
181    }
182
183    private static Map<String, String> merge( Map<String, String> dominant, Map<String, String> recessive )
184    {
185        Map<String, String> properties;
186
187        if ( ( dominant == null || dominant.isEmpty() ) && ( recessive == null || recessive.isEmpty() ) )
188        {
189            properties = Collections.emptyMap();
190        }
191        else
192        {
193            properties = new HashMap<String, String>();
194            if ( recessive != null )
195            {
196                properties.putAll( recessive );
197            }
198            if ( dominant != null )
199            {
200                properties.putAll( dominant );
201            }
202            properties = Collections.unmodifiableMap( properties );
203        }
204
205        return properties;
206    }
207
208    /**
209     * Creates a new artifact with the specified coordinates, properties and file. Passing {@code null} for any of the
210     * coordinates is equivalent to specifying an empty string.
211     * 
212     * @param groupId The group identifier of the artifact, may be {@code null}.
213     * @param artifactId The artifact identifier of the artifact, may be {@code null}.
214     * @param classifier The classifier of the artifact, may be {@code null}.
215     * @param extension The file extension of the artifact, may be {@code null}.
216     * @param version The version of the artifact, may be {@code null}.
217     * @param properties The properties of the artifact, may be {@code null} if none.
218     * @param file The resolved file of the artifact, may be {@code null}.
219     */
220    public DefaultArtifact( String groupId, String artifactId, String classifier, String extension, String version,
221                            Map<String, String> properties, File file )
222    {
223        this.groupId = emptify( groupId );
224        this.artifactId = emptify( artifactId );
225        this.classifier = emptify( classifier );
226        this.extension = emptify( extension );
227        this.version = emptify( version );
228        this.file = file;
229        this.properties = copyProperties( properties );
230    }
231
232    DefaultArtifact( String groupId, String artifactId, String classifier, String extension, String version, File file,
233                     Map<String, String> properties )
234    {
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    {
247        return ( str == null ) ? "" : str;
248    }
249
250    public String getGroupId()
251    {
252        return groupId;
253    }
254
255    public String getArtifactId()
256    {
257        return artifactId;
258    }
259
260    public String getVersion()
261    {
262        return version;
263    }
264
265    public String getClassifier()
266    {
267        return classifier;
268    }
269
270    public String getExtension()
271    {
272        return extension;
273    }
274
275    public File getFile()
276    {
277        return file;
278    }
279
280    public Map<String, String> getProperties()
281    {
282        return properties;
283    }
284
285}