blob: fb80df8c24345c9475d6763ad3b210c926909064 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.project.dependency;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.URLMapper;
/**
* Represents an artifact. Each artifact is identified by
* <ul>
* <li>group or organization id
* <li>artifact id
* <li>version
* <li>(optional) classifier; no classifier shall be interpreted as a
* regular build artifact
* <li>(optional) type; not type shall be interepreted as the default type
* for the processing compiler or builder
* </ul>
* The version specified is further classified by {@link VersionKind}, to
* distinguish versions possibly from repositories, development versions and
* floating versions.
* <p>
* The ArtifactSpec may provide additional tags, that can further describe the artifact,
* but those tags are not part of "identity" of the artifact, for dependencies or build
* systems, only
* <ul>
* <li>group
* <li>artifact
* <li>version
* <li>classifier
* <Li>extension
* </ul>
* are important.
*
* @author sdedic
*/
public final class ArtifactSpec<T> {
/**
* A tag for an artifact with basic output of the project's code/contents.
* You almost never want this, usually you want {@code null} classifier to
* identify the <b>default</b> output. But in rare cases you really do want
* to avoid post-processing or shading, this (abstract) classifier should
* identify an artifact before those steps.
* <p>
* If used in a query, a non-tagged artifact may be returned if the implementation
* does not support the tag.
*/
public static final String TAG_BASE = "<basic>"; // NOI18N
/**
* Tag for an artifact, that eventually contains dependencies bundled in. If used
* in a query, an ordinary (non-tagged) artifact may be returned from the query in case
* the implementation does not support the tag. Implementations may use additional, more
* specific tags on the returned artifacts.
*/
public static final String TAG_SHADED = "<shaded>";
/**
* Classifier for an artifact that contains sources.
*/
public static final String CLASSIFIER_SOURCES = "sources"; // NOI18N
/**
* Classifier for an artifact that contains test code
*/
public static final String CLASSIFIER_TESTS = "tests"; // NOI18N
/**
* Classifier for an artifact that contains test sources.
*/
public static final String CLASSIFIER_TEST_SOURCES = "test-sources"; // NOI18N
static final Logger LOG = Logger.getLogger(ProjectDependencies.class.getName());
/**
* Kind of the artifact version
*/
public enum VersionKind {
/**
* Regular publishable artifact
*/
REGULAR,
/**
* Snapshot artifact
*/
SNAPSHOT
};
private final VersionKind kind;
private final String type;
private final String groupId;
private final String artifactId;
private final String versionSpec;
private final String classifier;
private final boolean optional;
private final URI location;
// note: tags is NOT a part of hascode / equals, as externally only the classifier
// is visible, e.g. to the build system.
private final Set<String> tags;
private FileObject localFile;
final T data;
ArtifactSpec(VersionKind kind, String groupId, String artifactId, String versionSpec, String type, String classifier, boolean optional, URI location, FileObject localFile, Set<String> tags, T impl) {
this.kind = kind;
this.groupId = groupId;
this.artifactId = artifactId;
this.versionSpec = versionSpec;
this.classifier = classifier;
this.optional = optional;
this.data = impl;
this.type = type;
this.location = location;
this.localFile = localFile;
this.tags = tags == null ? Collections.emptySet() : tags;
}
public T getData() {
return data;
}
/**
* Returns local file which the artifact represents. For library (dependencies) artifacts,
* the file is the library consumed, e.g. from a local repository. For outputs, the artifact
* represents the build output, usually in project's build directory. Note that FileObject for
* a dependency and its corresponding Project may not be the same.
*
* @return
*/
public FileObject getLocalFile() {
// It's not locked well, but localFile will eventually become non-null, even though
// more lookups could be needed under contention.
FileObject f = localFile;
if (f == null) {
if (location != null) {
try {
synchronized (this) {
return this.localFile = URLMapper.findFileObject(location.toURL());
}
} catch (MalformedURLException ex) {
LOG.log(Level.WARNING, "Artifact location cannot be converted to URL: {0}", location);
}
f = localFile = FileUtil.getConfigRoot();
}
}
return f == FileUtil.getConfigRoot() ? null : f;
}
public boolean hasTag(String tag) {
return tags.contains(tag);
}
public URI getLocation() {
return location;
}
public VersionKind getKind() {
return kind;
}
public String getType() {
return type;
}
public String getGroupId() {
return groupId;
}
public String getArtifactId() {
return artifactId;
}
public String getVersionSpec() {
return versionSpec;
}
public String getClassifier() {
return classifier;
}
public boolean isOptional() {
return optional;
}
@Override
public int hashCode() {
int hash = 7;
hash = 79 * hash + Objects.hashCode(this.kind);
hash = 79 * hash + Objects.hashCode(this.type);
hash = 79 * hash + Objects.hashCode(this.groupId);
hash = 79 * hash + Objects.hashCode(this.artifactId);
hash = 79 * hash + Objects.hashCode(this.versionSpec);
hash = 79 * hash + Objects.hashCode(this.classifier);
hash = 79 * hash + Objects.hashCode(this.location);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ArtifactSpec<?> other = (ArtifactSpec<?>) obj;
if (!Objects.equals(this.type, other.type)) {
return false;
}
if (!Objects.equals(this.groupId, other.groupId)) {
return false;
}
if (!Objects.equals(this.artifactId, other.artifactId)) {
return false;
}
if (!Objects.equals(this.versionSpec, other.versionSpec)) {
return false;
}
if (!Objects.equals(this.classifier, other.classifier)) {
return false;
}
if (!Objects.equals(this.location, other.location)) {
return false;
}
return this.kind == other.kind;
}
public String toString() {
StringBuilder sb = new StringBuilder(
String.format("%s:%s:%s", getGroupId(), getArtifactId(), getVersionSpec() == null ? "" : getVersionSpec())
);
if (classifier != null) {
sb.append(":").append(classifier);
}
if (type != null) {
sb.append("[").append(type).append("]");
}
if (optional) {
sb.append("?");
}
return sb.toString();
}
/**
* Returns opaque project-specific data. If searching for
* a project-specific extension, use {@link ProjectDependencies#findAdapters} instead.
*
* @return unspecified underlying project data
*/
public T getProjectData() {
return data;
}
public static <V> ArtifactSpec<V> createVersionSpec(
@NullAllowed String groupId, @NonNull String artifactId,
@NullAllowed String type, @NullAllowed String classifier,
@NonNull String versionSpec, boolean optional, @NullAllowed FileObject localFile, @NonNull V data) {
URL u = localFile == null ? null : URLMapper.findURL(localFile, URLMapper.EXTERNAL);
URI uri = null;
if (u != null) {
try {
uri = u.toURI();
} catch (URISyntaxException ex) {
// should not happen
}
}
return new ArtifactSpec<V>(VersionKind.REGULAR, groupId, artifactId, versionSpec, type, classifier, optional, uri, localFile, Collections.emptySet(), data);
}
public static <V> ArtifactSpec<V> createSnapshotSpec(
@NonNull String groupId, @NonNull String artifactId,
@NullAllowed String type, @NullAllowed String classifier,
@NonNull String versionSpec, boolean optional, @NullAllowed FileObject localFile, @NonNull V data) {
URI uri = null;
if (localFile != null) {
URL u = URLMapper.findURL(localFile, URLMapper.EXTERNAL);
if (u != null) {
try {
uri = u.toURI();
} catch (URISyntaxException ex) {
// should not happen
}
}
}
return new ArtifactSpec<V>(VersionKind.SNAPSHOT, groupId, artifactId, versionSpec, type, classifier, optional, uri, localFile, Collections.emptySet(), data);
}
public static final <T> Builder<T> builder(String group, String artifact, String version, T projectData) {
return new Builder(group, artifact, version, projectData);
}
public final static class Builder<T> {
private final T data;
private final String groupId;
private final String artifactId;
private final String versionSpec;
private VersionKind kind = VersionKind.REGULAR;
private String type;
private String classifier;
private boolean optional;
private FileObject localFile;
private URI location;
private Set<String> tags;
public Builder(String groupId, String artifactId, String versionSpec, T data) {
this.groupId = groupId;
this.artifactId = artifactId;
this.versionSpec = versionSpec;
this.data = data;
}
public Builder type(String type) {
this.type = type;
return this;
}
public Builder classifier(String classifier) {
this.classifier = classifier;
return this;
}
public Builder optional(boolean optional) {
this.optional = optional;
return this;
}
public Builder localFile(FileObject localFile) {
this.localFile = localFile;
return this;
}
public Builder tag(String tag) {
if (tags == null) {
tags = new HashSet<>();
}
tags.add(tag);
return this;
}
public Builder tags(String... tags) {
if (tags == null || tags.length == 0) {
return this;
} else {
for (String t : tags) {
tag(t);
}
return this;
}
}
/**
* Forces the local file reference. Unlike {@link #localFile}, if {@code null} is
* passed, the {@link ArtifactSpec#getLocalFile()} will not attempt to resole the URI
* to a FileObject. Might be useful to indicate that no file was known <b>at the time
* of the ArtifactSpec creation</b>
* @param localFile the local file, {@code null} to disallows URI to FileObject implicit conversion.
* @return builder instance.
*/
public Builder forceLocalFile(FileObject localFile) {
this.localFile = localFile == null ?FileUtil.getConfigRoot() : localFile;
return this;
}
public Builder location(URI location) {
this.location = location;
return this;
}
public ArtifactSpec build() {
return new ArtifactSpec(kind, groupId, artifactId, versionSpec, type, classifier, optional, location, localFile, tags, data);
}
}
}