| /* |
| * 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); |
| } |
| } |
| } |