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.nio.file.Path;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.Map;
026import java.util.Objects;
027import java.util.regex.Matcher;
028import java.util.regex.Pattern;
029
030/**
031 * A skeleton class for artifacts.
032 */
033public abstract class AbstractArtifact implements Artifact {
034
035    private static final String SNAPSHOT = "SNAPSHOT";
036
037    private static final Pattern SNAPSHOT_TIMESTAMP = Pattern.compile("^(.*-)?([0-9]{8}\\.[0-9]{6}-[0-9]+)$");
038
039    /**
040     * Constructor for subclasses.
041     */
042    public AbstractArtifact() {}
043
044    @Override
045    public boolean isSnapshot() {
046        return isSnapshot(getVersion());
047    }
048
049    private static boolean isSnapshot(String version) {
050        return version.endsWith(SNAPSHOT) || SNAPSHOT_TIMESTAMP.matcher(version).matches();
051    }
052
053    @Override
054    public String getBaseVersion() {
055        return toBaseVersion(getVersion());
056    }
057
058    private static String toBaseVersion(String version) {
059        String baseVersion;
060
061        if (version == null) {
062            baseVersion = null;
063        } else if (version.startsWith("[") || version.startsWith("(")) {
064            baseVersion = version;
065        } else {
066            Matcher m = SNAPSHOT_TIMESTAMP.matcher(version);
067            if (m.matches()) {
068                if (m.group(1) != null) {
069                    baseVersion = m.group(1) + SNAPSHOT;
070                } else {
071                    baseVersion = SNAPSHOT;
072                }
073            } else {
074                baseVersion = version;
075            }
076        }
077
078        return baseVersion;
079    }
080
081    /**
082     * Creates a new artifact with the specified coordinates, properties and file.
083     *
084     * @param version The version of the artifact, may be {@code null}.
085     * @param properties The properties of the artifact, may be {@code null} if none. The method may assume immutability
086     *            of the supplied map, i.e. need not copy it.
087     * @param path The resolved file of the artifact, may be {@code null}.
088     * @return The new artifact instance, never {@code null}.
089     */
090    private Artifact newInstance(String version, Map<String, String> properties, Path path) {
091        return new DefaultArtifact(
092                getGroupId(), getArtifactId(), getClassifier(), getExtension(), version, path, properties);
093    }
094
095    @Override
096    public Artifact setVersion(String version) {
097        String current = getVersion();
098        if (current.equals(version) || (version == null && current.isEmpty())) {
099            return this;
100        }
101        return newInstance(version, getProperties(), getPath());
102    }
103
104    /**
105     * This method should (and in Resolver is) overridden, but is kept just to preserve backward compatibility if
106     * this class is extended somewhere.
107     */
108    public Path getPath() {
109        File file = getFile();
110        return file != null ? file.toPath() : null;
111    }
112
113    @Deprecated
114    @Override
115    public Artifact setFile(File file) {
116        return setPath(file != null ? file.toPath() : null);
117    }
118
119    @Override
120    public Artifact setPath(Path path) {
121        Path current = getPath();
122        if (Objects.equals(current, path)) {
123            return this;
124        }
125        return newInstance(getVersion(), getProperties(), path);
126    }
127
128    public Artifact setProperties(Map<String, String> properties) {
129        Map<String, String> current = getProperties();
130        if (current.equals(properties) || (properties == null && current.isEmpty())) {
131            return this;
132        }
133        return newInstance(getVersion(), copyProperties(properties), getPath());
134    }
135
136    public String getProperty(String key, String defaultValue) {
137        String value = getProperties().get(key);
138        return (value != null) ? value : defaultValue;
139    }
140
141    /**
142     * Copies the specified artifact properties. This utility method should be used when creating new artifact instances
143     * with caller-supplied properties.
144     *
145     * @param properties The properties to copy, may be {@code null}.
146     * @return The copied and read-only properties, never {@code null}.
147     */
148    protected static Map<String, String> copyProperties(Map<String, String> properties) {
149        if (properties != null && !properties.isEmpty()) {
150            return Collections.unmodifiableMap(new HashMap<>(properties));
151        } else {
152            return Collections.emptyMap();
153        }
154    }
155
156    @Override
157    public String toString() {
158        StringBuilder buffer = new StringBuilder(128);
159        buffer.append(getGroupId());
160        buffer.append(':').append(getArtifactId());
161        buffer.append(':').append(getExtension());
162        if (!getClassifier().isEmpty()) {
163            buffer.append(':').append(getClassifier());
164        }
165        buffer.append(':').append(getVersion());
166        return buffer.toString();
167    }
168
169    /**
170     * Compares this artifact with the specified object.
171     *
172     * @param obj The object to compare this artifact against, may be {@code null}.
173     * @return {@code true} if and only if the specified object is another {@link Artifact} with equal coordinates,
174     *         properties and file, {@code false} otherwise.
175     */
176    @Override
177    public boolean equals(Object obj) {
178        if (obj == this) {
179            return true;
180        } else if (!(obj instanceof Artifact)) {
181            return false;
182        }
183
184        Artifact that = (Artifact) obj;
185
186        return Objects.equals(getArtifactId(), that.getArtifactId())
187                && Objects.equals(getGroupId(), that.getGroupId())
188                && Objects.equals(getVersion(), that.getVersion())
189                && Objects.equals(getExtension(), that.getExtension())
190                && Objects.equals(getClassifier(), that.getClassifier())
191                && Objects.equals(getPath(), that.getPath())
192                && Objects.equals(getProperties(), that.getProperties());
193    }
194
195    /**
196     * Returns a hash code for this artifact.
197     *
198     * @return A hash code for the artifact.
199     */
200    @Override
201    public int hashCode() {
202        int hash = 17;
203        hash = hash * 31 + getGroupId().hashCode();
204        hash = hash * 31 + getArtifactId().hashCode();
205        hash = hash * 31 + getExtension().hashCode();
206        hash = hash * 31 + getClassifier().hashCode();
207        hash = hash * 31 + getVersion().hashCode();
208        hash = hash * 31 + hash(getPath());
209        return hash;
210    }
211
212    private static int hash(Object obj) {
213        return (obj != null) ? obj.hashCode() : 0;
214    }
215}