Sources.java
/*
* 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.apache.maven.api.services;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import org.apache.maven.api.annotations.Experimental;
import org.apache.maven.api.annotations.Nonnull;
import org.apache.maven.api.annotations.Nullable;
import org.apache.maven.api.cache.CacheMetadata;
import org.apache.maven.api.cache.CacheRetention;
import static java.util.Objects.requireNonNull;
/**
* Factory methods for creating different types of sources.
* <p>
* This class provides specialized source implementations for different use cases:
* <ul>
* <li>Path sources - simple access to file content</li>
* <li>Build sources - POM files being actively built by Maven</li>
* <li>Resolved sources - POMs resolved from repositories</li>
* </ul>
*
* @since 4.0.0
*/
@Experimental
public final class Sources {
private Sources() {}
/**
* Creates a new source for the specified path.
*
* @param path the path to the file
* @return a new Source instance
* @throws NullPointerException if path is null
*/
@Nonnull
public static Source fromPath(@Nonnull Path path) {
return new PathSource(requireNonNull(path, "path"));
}
/**
* Creates a new build source for the specified path.
* Build sources are used for POM files of projects being built by Maven
* in the filesystem and support resolving related POMs.
*
* @param path the path to the POM file or project directory
* @return a new ModelSource instance configured as a build source
* @throws NullPointerException if path is null
*/
@Nonnull
public static ModelSource buildSource(@Nonnull Path path) {
return new BuildPathSource(requireNonNull(path, "path"));
}
/**
* Creates a new resolved source for the specified path and location.
* Resolved sources are used for artifacts that have been resolved by Maven
* from repositories (using groupId:artifactId:version coordinates) and
* downloaded to the local repository. These sources do not support resolving
* other sources.
*
* @param path the path to the POM file or project directory
* @param location optional logical location of the source, used for reporting purposes
* @return a new ModelSource instance configured as a resolved source
* @throws NullPointerException if path is null
*/
@Nonnull
public static ModelSource resolvedSource(@Nonnull Path path, @Nullable String location) {
return new ResolvedPathSource(requireNonNull(path, "path"), location);
}
/**
* Basic implementation of {@link Source} that uses a {@link Path} as the underlying source.
*/
static class PathSource implements Source {
@Nonnull
protected final Path path;
@Nonnull
protected final String location;
/**
* Constructs a new PathSource with the specified path.
*
* @param path the filesystem path to the source content
* @throws NullPointerException if path is null
*/
PathSource(Path path) {
this(path, null);
}
/**
* Constructs a new PathSource with the specified path and location.
*
* @param path the filesystem path to the source content
* @param location the logical location of the source, used for reporting purposes.
* If null, the path string representation is used
*/
protected PathSource(Path path, String location) {
this.path = requireNonNull(path, "path").normalize();
this.location = location != null ? location : this.path.toString();
}
@Override
@Nullable
public Path getPath() {
return path;
}
@Override
@Nonnull
public InputStream openStream() throws IOException {
return Files.newInputStream(path);
}
@Override
@Nonnull
public String getLocation() {
return location;
}
@Override
@Nullable
public Source resolve(@Nonnull String relative) {
return new PathSource(path.resolve(relative));
}
@Override
public boolean equals(Object o) {
return o == this || o instanceof PathSource that && Objects.equals(path, that.path);
}
@Override
public int hashCode() {
return Objects.hash(path);
}
@Override
public String toString() {
return getClass().getSimpleName() + "[location='" + location + "', path=" + path + ']';
}
}
/**
* Implementation of {@link ModelSource} for POM files that have been resolved
* from repositories. Does not support resolving related sources.
*/
static class ResolvedPathSource extends PathSource implements ModelSource {
ResolvedPathSource(Path path, String location) {
super(path, location);
}
@Override
public Path getPath() {
return null;
}
@Override
public Source resolve(String relative) {
return null;
}
@Override
@Nullable
public ModelSource resolve(@Nonnull ModelLocator modelLocator, @Nonnull String relative) {
return null;
}
}
/**
* Implementation of {@link ModelSource} that extends {@link PathSource} with model-specific
* functionality. This implementation uses request-scoped caching ({@link CacheRetention#REQUEST_SCOPED})
* since it represents a POM file that is actively being built and may change during the build process.
* <p>
* The request-scoped retention policy ensures that:
* <ul>
* <li>Changes to the POM file during the build are detected</li>
* <li>Cache entries don't persist beyond the current build request</li>
* <li>Memory is freed once the build request completes</li>
* </ul>
*/
static class BuildPathSource extends PathSource implements ModelSource, CacheMetadata {
/**
* Constructs a new ModelPathSource.
*
* @param path the filesystem path to the source content
*/
BuildPathSource(Path path) {
super(path, null);
}
@Override
public Path getPath() {
return path;
}
@Override
public Source resolve(@Nonnull String relative) {
return new BuildPathSource(path.resolve(relative));
}
@Override
@Nullable
public ModelSource resolve(@Nonnull ModelLocator locator, @Nonnull String relative) {
String norm = relative.replace('\\', File.separatorChar).replace('/', File.separatorChar);
Path path = getPath().getParent().resolve(norm);
Path relatedPom = locator.locateExistingPom(path);
if (relatedPom != null) {
return new BuildPathSource(relatedPom);
}
return null;
}
@Override
public CacheRetention getCacheRetention() {
return CacheRetention.REQUEST_SCOPED;
}
}
}